@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
|
@@ -7,8 +7,6 @@ export interface TerminalSession {
|
|
|
7
7
|
websocket?: WebSocket;
|
|
8
8
|
createdAt: Date;
|
|
9
9
|
lastActivity: Date;
|
|
10
|
-
enableLogging?: boolean;
|
|
11
|
-
logFilePath?: string;
|
|
12
10
|
backlog: string;
|
|
13
11
|
previewSockets?: Set<WebSocket>;
|
|
14
12
|
previewBuffer?: string;
|
|
@@ -21,7 +19,7 @@ export declare class TerminalManager {
|
|
|
21
19
|
private cleanupInterval;
|
|
22
20
|
private static readonly MAX_BACKLOG_BYTES;
|
|
23
21
|
constructor();
|
|
24
|
-
createSession(proposalId: string,
|
|
22
|
+
createSession(proposalId: string, shellPreference?: ShellType, startupCommand?: string): Promise<TerminalSession>;
|
|
25
23
|
getSession(sessionId: string): TerminalSession | undefined;
|
|
26
24
|
getSessionsByProposal(proposalId: string): TerminalSession[];
|
|
27
25
|
attachWebSocket(sessionId: string, ws: WebSocket): boolean;
|
|
@@ -16,7 +16,7 @@ export class TerminalManager {
|
|
|
16
16
|
this.cleanupInactiveSessions();
|
|
17
17
|
}, 5 * 60 * 1000);
|
|
18
18
|
}
|
|
19
|
-
async createSession(proposalId,
|
|
19
|
+
async createSession(proposalId, shellPreference = 'bash', startupCommand) {
|
|
20
20
|
const sessionId = `${proposalId}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
21
21
|
// Get the project root directory
|
|
22
22
|
let projectRoot;
|
|
@@ -65,22 +65,6 @@ export class TerminalManager {
|
|
|
65
65
|
catch (error) {
|
|
66
66
|
console.error('Error creating init script:', error);
|
|
67
67
|
}
|
|
68
|
-
// Set up logging if enabled
|
|
69
|
-
let logFilePath;
|
|
70
|
-
if (enableLogging) {
|
|
71
|
-
const sessionsDir = path.join(projectRoot, '.nut', 'sessions');
|
|
72
|
-
try {
|
|
73
|
-
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
74
|
-
logFilePath = path.join(sessionsDir, `${sessionId}.log`);
|
|
75
|
-
// Create log file with session header
|
|
76
|
-
const logHeader = `# Terminal Session Log\n# Session ID: ${sessionId}\n# Proposal ID: ${proposalId}\n# Created: ${new Date().toISOString()}\n\n`;
|
|
77
|
-
fs.writeFileSync(logFilePath, logHeader);
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
console.error('Error setting up session logging:', error);
|
|
81
|
-
logFilePath = undefined;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
68
|
// Get shell arguments
|
|
85
69
|
const shellArgs = getShellArgs(shellPath, initScriptPath);
|
|
86
70
|
// Prepare environment variables with API keys injected.
|
|
@@ -120,8 +104,6 @@ export class TerminalManager {
|
|
|
120
104
|
pty: ptyProcess,
|
|
121
105
|
createdAt: new Date(),
|
|
122
106
|
lastActivity: new Date(),
|
|
123
|
-
enableLogging,
|
|
124
|
-
logFilePath,
|
|
125
107
|
backlog: '',
|
|
126
108
|
previewSockets: new Set(),
|
|
127
109
|
previewBuffer: '',
|
|
@@ -153,17 +135,6 @@ export class TerminalManager {
|
|
|
153
135
|
// Set up PTY event handlers
|
|
154
136
|
ptyProcess.onData((data) => {
|
|
155
137
|
session.lastActivity = new Date();
|
|
156
|
-
// Log data if logging is enabled
|
|
157
|
-
if (session.enableLogging && session.logFilePath) {
|
|
158
|
-
try {
|
|
159
|
-
const timestamp = new Date().toISOString();
|
|
160
|
-
const logEntry = `[${timestamp}] OUTPUT: ${data}`;
|
|
161
|
-
fs.appendFileSync(session.logFilePath, logEntry);
|
|
162
|
-
}
|
|
163
|
-
catch (error) {
|
|
164
|
-
console.error('Error writing to session log:', error);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
138
|
// Maintain in-memory backlog (trim to last MAX_BACKLOG_BYTES)
|
|
168
139
|
try {
|
|
169
140
|
// Append and trim to max size
|
|
@@ -187,17 +158,6 @@ export class TerminalManager {
|
|
|
187
158
|
ptyProcess.onExit((e) => {
|
|
188
159
|
const endTime = new Date();
|
|
189
160
|
const duration = endTime.getTime() - session.createdAt.getTime();
|
|
190
|
-
// Log session end if logging is enabled
|
|
191
|
-
if (session.enableLogging && session.logFilePath) {
|
|
192
|
-
try {
|
|
193
|
-
const timestamp = endTime.toISOString();
|
|
194
|
-
const logEntry = `\n[${timestamp}] SESSION ENDED: Exit code ${e.exitCode}\n`;
|
|
195
|
-
fs.appendFileSync(session.logFilePath, logEntry);
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
console.error('Error writing session end to log:', error);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
161
|
// Log session end to activity log
|
|
202
162
|
try {
|
|
203
163
|
const logger = getLogger();
|
|
@@ -289,17 +249,6 @@ export class TerminalManager {
|
|
|
289
249
|
const data = JSON.parse(message.toString());
|
|
290
250
|
switch (data.type) {
|
|
291
251
|
case 'input':
|
|
292
|
-
// Log input if logging is enabled
|
|
293
|
-
if (session.enableLogging && session.logFilePath) {
|
|
294
|
-
try {
|
|
295
|
-
const timestamp = new Date().toISOString();
|
|
296
|
-
const logEntry = `[${timestamp}] INPUT: ${data.data}`;
|
|
297
|
-
fs.appendFileSync(session.logFilePath, logEntry);
|
|
298
|
-
}
|
|
299
|
-
catch (error) {
|
|
300
|
-
console.error('Error writing input to session log:', error);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
252
|
session.pty.write(data.data);
|
|
304
253
|
session.lastActivity = new Date();
|
|
305
254
|
break;
|
|
@@ -7,7 +7,7 @@ import { ZodError } from 'zod';
|
|
|
7
7
|
import { streamText, tool, jsonSchema, stepCountIs } from 'ai';
|
|
8
8
|
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
9
9
|
import { getLogsDir, listProposals, getProposal, createProposal, updateProposal, deleteProposal, } from '@lovelybunch/core';
|
|
10
|
-
import { proposalsFullTool, knowledgeTool, normalizeKnowledgeMetadata, eventsTool, projectContextTool, architectureContextTool, roleContextTool } from '@lovelybunch/mcp';
|
|
10
|
+
import { proposalsFullTool, knowledgeTool, normalizeKnowledgeMetadata, eventsTool, projectContextTool, architectureContextTool, roleContextTool, resourcesTool } from '@lovelybunch/mcp';
|
|
11
11
|
import matter from 'gray-matter';
|
|
12
12
|
import Fuse from 'fuse.js';
|
|
13
13
|
import { FileStorageAdapter } from '../../../../lib/storage/file-storage.js';
|
|
@@ -144,6 +144,14 @@ export async function POST(c) {
|
|
|
144
144
|
return JSON.stringify(result);
|
|
145
145
|
},
|
|
146
146
|
}),
|
|
147
|
+
resources: tool({
|
|
148
|
+
description: resourcesTool.description,
|
|
149
|
+
inputSchema: jsonSchema(resourcesTool.parameters),
|
|
150
|
+
execute: async (args) => {
|
|
151
|
+
const result = await executeResourcesToolDirect(args);
|
|
152
|
+
return JSON.stringify(result);
|
|
153
|
+
},
|
|
154
|
+
}),
|
|
147
155
|
};
|
|
148
156
|
// Debug logging
|
|
149
157
|
console.log('AI Request Debug:', {
|
|
@@ -817,6 +825,319 @@ async function executeRoleContextToolDirect(args) {
|
|
|
817
825
|
return { success: false, error: error.message || 'Role context tool execution failed' };
|
|
818
826
|
}
|
|
819
827
|
}
|
|
828
|
+
// ─── Resources Tool ─────────────────────────────────────────────────────────
|
|
829
|
+
// Uses internal HTTP calls to the same server's resource endpoints so that all
|
|
830
|
+
// existing validation, thumbnail generation, and error handling is reused.
|
|
831
|
+
function getInternalApiBase() {
|
|
832
|
+
const port = process.env.PORT ? parseInt(process.env.PORT) : 3001;
|
|
833
|
+
return `http://127.0.0.1:${port}`;
|
|
834
|
+
}
|
|
835
|
+
async function executeResourcesToolDirect(args) {
|
|
836
|
+
const { operation, query, type_filter, resource_id, prompt, model, aspect_ratio, text, voice, duration, resolution, url, tags, description } = args;
|
|
837
|
+
const apiBase = getInternalApiBase();
|
|
838
|
+
try {
|
|
839
|
+
switch (operation) {
|
|
840
|
+
case 'list': {
|
|
841
|
+
const response = await fetch(`${apiBase}/api/v1/resources`);
|
|
842
|
+
const result = await response.json();
|
|
843
|
+
if (!result.success || !result.data) {
|
|
844
|
+
return { success: false, error: 'Failed to list resources' };
|
|
845
|
+
}
|
|
846
|
+
let resources = result.data;
|
|
847
|
+
// Apply type filter
|
|
848
|
+
if (type_filter) {
|
|
849
|
+
const prefix = type_filter.toLowerCase();
|
|
850
|
+
resources = resources.filter((r) => r.type?.toLowerCase().startsWith(prefix));
|
|
851
|
+
}
|
|
852
|
+
// Apply search filter
|
|
853
|
+
if (query) {
|
|
854
|
+
const q = query.toLowerCase();
|
|
855
|
+
resources = resources.filter((r) => r.name?.toLowerCase().includes(q) ||
|
|
856
|
+
r.metadata?.description?.toLowerCase().includes(q) ||
|
|
857
|
+
r.metadata?.tags?.some((tag) => tag.toLowerCase().includes(q)));
|
|
858
|
+
}
|
|
859
|
+
// Return a concise summary to keep token usage reasonable
|
|
860
|
+
const summary = resources.map((r) => ({
|
|
861
|
+
id: r.id,
|
|
862
|
+
name: r.name,
|
|
863
|
+
type: r.type,
|
|
864
|
+
size: r.size,
|
|
865
|
+
tags: r.metadata?.tags,
|
|
866
|
+
description: r.metadata?.description
|
|
867
|
+
? (r.metadata.description.length > 100
|
|
868
|
+
? r.metadata.description.slice(0, 100) + '...'
|
|
869
|
+
: r.metadata.description)
|
|
870
|
+
: undefined,
|
|
871
|
+
}));
|
|
872
|
+
return {
|
|
873
|
+
success: true,
|
|
874
|
+
data: summary,
|
|
875
|
+
count: summary.length,
|
|
876
|
+
message: query
|
|
877
|
+
? `Found ${summary.length} resources matching "${query}"`
|
|
878
|
+
: `Found ${summary.length} resources`
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
case 'get': {
|
|
882
|
+
if (!resource_id) {
|
|
883
|
+
return { success: false, error: 'resource_id is required for get operation' };
|
|
884
|
+
}
|
|
885
|
+
const response = await fetch(`${apiBase}/api/v1/resources`);
|
|
886
|
+
const result = await response.json();
|
|
887
|
+
if (!result.success || !result.data) {
|
|
888
|
+
return { success: false, error: 'Failed to fetch resources' };
|
|
889
|
+
}
|
|
890
|
+
const resource = result.data.find((r) => r.id === resource_id);
|
|
891
|
+
if (!resource) {
|
|
892
|
+
return { success: false, error: `Resource '${resource_id}' not found` };
|
|
893
|
+
}
|
|
894
|
+
return {
|
|
895
|
+
success: true,
|
|
896
|
+
data: resource,
|
|
897
|
+
message: `Retrieved resource ${resource.name} (${resource.id})`
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
case 'generate_image': {
|
|
901
|
+
if (!prompt) {
|
|
902
|
+
return { success: false, error: 'prompt is required for generate_image operation' };
|
|
903
|
+
}
|
|
904
|
+
// Step 1: Generate the image via Replicate
|
|
905
|
+
const genBody = {
|
|
906
|
+
prompt,
|
|
907
|
+
model: model || 'nano-banana-pro',
|
|
908
|
+
dimensions: aspect_ratio || '16:9',
|
|
909
|
+
output_format: 'png',
|
|
910
|
+
};
|
|
911
|
+
const genResponse = await fetch(`${apiBase}/api/v1/resources/generate`, {
|
|
912
|
+
method: 'POST',
|
|
913
|
+
headers: { 'Content-Type': 'application/json' },
|
|
914
|
+
body: JSON.stringify(genBody),
|
|
915
|
+
signal: AbortSignal.timeout(300_000), // 5 min
|
|
916
|
+
});
|
|
917
|
+
const genResult = await genResponse.json();
|
|
918
|
+
if (!genResult.success || !genResult.data?.imageUrl) {
|
|
919
|
+
const msg = genResult.error?.message || 'Image generation failed';
|
|
920
|
+
return { success: false, error: msg };
|
|
921
|
+
}
|
|
922
|
+
// Step 2: Download and save to resources
|
|
923
|
+
const saved = await downloadAndSaveToResources(genResult.data.imageUrl, apiBase, { type: 'image', prompt, model: model || 'nano-banana-pro', tags, description });
|
|
924
|
+
return {
|
|
925
|
+
success: true,
|
|
926
|
+
data: saved,
|
|
927
|
+
message: `Generated and saved image "${saved.name}" (${saved.id}). It is now available in the Resources section.`
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
case 'generate_audio': {
|
|
931
|
+
if (!text) {
|
|
932
|
+
return { success: false, error: 'text is required for generate_audio operation' };
|
|
933
|
+
}
|
|
934
|
+
const audioBody = {
|
|
935
|
+
text,
|
|
936
|
+
voice_id: voice || 'English_Trustworth_Man',
|
|
937
|
+
speed: 1,
|
|
938
|
+
pitch: 0,
|
|
939
|
+
emotion: 'auto',
|
|
940
|
+
language_boost: 'English',
|
|
941
|
+
audio_format: 'mp3',
|
|
942
|
+
};
|
|
943
|
+
const audioResponse = await fetch(`${apiBase}/api/v1/resources/generate-audio`, {
|
|
944
|
+
method: 'POST',
|
|
945
|
+
headers: { 'Content-Type': 'application/json' },
|
|
946
|
+
body: JSON.stringify(audioBody),
|
|
947
|
+
signal: AbortSignal.timeout(300_000),
|
|
948
|
+
});
|
|
949
|
+
const audioResult = await audioResponse.json();
|
|
950
|
+
if (!audioResult.success || !audioResult.data?.audioUrl) {
|
|
951
|
+
const msg = audioResult.error?.message || 'Audio generation failed';
|
|
952
|
+
return { success: false, error: msg };
|
|
953
|
+
}
|
|
954
|
+
const savedAudio = await downloadAndSaveToResources(audioResult.data.audioUrl, apiBase, { type: 'audio', prompt: text, model: 'minimax/speech-02-turbo', tags, description });
|
|
955
|
+
return {
|
|
956
|
+
success: true,
|
|
957
|
+
data: savedAudio,
|
|
958
|
+
message: `Generated and saved audio "${savedAudio.name}" (${savedAudio.id}). It is now available in the Resources section.`
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
case 'generate_video': {
|
|
962
|
+
if (!prompt) {
|
|
963
|
+
return { success: false, error: 'prompt is required for generate_video operation' };
|
|
964
|
+
}
|
|
965
|
+
const videoBody = {
|
|
966
|
+
prompt,
|
|
967
|
+
duration: duration || 8,
|
|
968
|
+
resolution: resolution || '720p',
|
|
969
|
+
aspect_ratio: aspect_ratio || '16:9',
|
|
970
|
+
generate_audio: true,
|
|
971
|
+
};
|
|
972
|
+
const videoResponse = await fetch(`${apiBase}/api/v1/resources/generate-video`, {
|
|
973
|
+
method: 'POST',
|
|
974
|
+
headers: { 'Content-Type': 'application/json' },
|
|
975
|
+
body: JSON.stringify(videoBody),
|
|
976
|
+
signal: AbortSignal.timeout(300_000),
|
|
977
|
+
});
|
|
978
|
+
const videoResult = await videoResponse.json();
|
|
979
|
+
if (!videoResult.success || !videoResult.data?.videoUrl) {
|
|
980
|
+
const msg = videoResult.error?.message || 'Video generation failed';
|
|
981
|
+
return { success: false, error: msg };
|
|
982
|
+
}
|
|
983
|
+
const savedVideo = await downloadAndSaveToResources(videoResult.data.videoUrl, apiBase, { type: 'video', prompt, model: 'google/veo-3.1-fast', tags, description });
|
|
984
|
+
return {
|
|
985
|
+
success: true,
|
|
986
|
+
data: savedVideo,
|
|
987
|
+
message: `Generated and saved video "${savedVideo.name}" (${savedVideo.id}). It is now available in the Resources section.`
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
case 'add_from_url': {
|
|
991
|
+
if (!url) {
|
|
992
|
+
return { success: false, error: 'url is required for add_from_url operation' };
|
|
993
|
+
}
|
|
994
|
+
// Download the URL
|
|
995
|
+
const dlResponse = await fetch(url);
|
|
996
|
+
if (!dlResponse.ok) {
|
|
997
|
+
return { success: false, error: `Failed to download URL: ${dlResponse.statusText}` };
|
|
998
|
+
}
|
|
999
|
+
const buffer = Buffer.from(await dlResponse.arrayBuffer());
|
|
1000
|
+
const contentType = dlResponse.headers.get('content-type') || 'application/octet-stream';
|
|
1001
|
+
// Derive filename from URL path
|
|
1002
|
+
let fileName;
|
|
1003
|
+
try {
|
|
1004
|
+
const urlPath = new URL(url).pathname;
|
|
1005
|
+
fileName = basename(urlPath);
|
|
1006
|
+
if (!fileName || fileName === '/') {
|
|
1007
|
+
fileName = `downloaded-${Date.now()}`;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
catch {
|
|
1011
|
+
fileName = `downloaded-${Date.now()}`;
|
|
1012
|
+
}
|
|
1013
|
+
// Add extension from content-type if missing
|
|
1014
|
+
if (!fileName.includes('.')) {
|
|
1015
|
+
const extMap = {
|
|
1016
|
+
'image/png': '.png', 'image/jpeg': '.jpg', 'image/webp': '.webp',
|
|
1017
|
+
'image/gif': '.gif', 'image/svg+xml': '.svg',
|
|
1018
|
+
'audio/mpeg': '.mp3', 'audio/wav': '.wav', 'audio/ogg': '.ogg',
|
|
1019
|
+
'video/mp4': '.mp4', 'video/webm': '.webm',
|
|
1020
|
+
'application/pdf': '.pdf', 'application/json': '.json',
|
|
1021
|
+
};
|
|
1022
|
+
const ext = Object.entries(extMap).find(([ct]) => contentType.includes(ct))?.[1] || '.bin';
|
|
1023
|
+
fileName += ext;
|
|
1024
|
+
}
|
|
1025
|
+
const mimeType = contentType.split(';')[0].trim();
|
|
1026
|
+
// Upload to resources via FormData
|
|
1027
|
+
const formData = new FormData();
|
|
1028
|
+
const blob = new Blob([buffer], { type: mimeType });
|
|
1029
|
+
formData.set('file', blob, fileName);
|
|
1030
|
+
if (tags)
|
|
1031
|
+
formData.set('tags', tags);
|
|
1032
|
+
if (description)
|
|
1033
|
+
formData.set('description', description);
|
|
1034
|
+
const uploadResponse = await fetch(`${apiBase}/api/v1/resources`, {
|
|
1035
|
+
method: 'POST',
|
|
1036
|
+
body: formData,
|
|
1037
|
+
});
|
|
1038
|
+
const uploadResult = await uploadResponse.json();
|
|
1039
|
+
if (!uploadResult.success || !uploadResult.data) {
|
|
1040
|
+
return { success: false, error: uploadResult.error?.message || 'Upload failed' };
|
|
1041
|
+
}
|
|
1042
|
+
return {
|
|
1043
|
+
success: true,
|
|
1044
|
+
data: uploadResult.data,
|
|
1045
|
+
message: `Downloaded and saved "${uploadResult.data.name}" (${uploadResult.data.id}) from URL. It is now available in the Resources section.`
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
default:
|
|
1049
|
+
return {
|
|
1050
|
+
success: false,
|
|
1051
|
+
error: `Unknown operation: ${operation}. Supported: list, get, generate_image, generate_audio, generate_video, add_from_url`
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
catch (error) {
|
|
1056
|
+
console.error('Error executing resources tool:', error);
|
|
1057
|
+
return { success: false, error: error.message || 'Resources tool execution failed' };
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Download media from a URL and upload it to the resources API.
|
|
1062
|
+
* Used by generation operations to auto-save generated content.
|
|
1063
|
+
*/
|
|
1064
|
+
async function downloadAndSaveToResources(mediaUrl, apiBase, meta) {
|
|
1065
|
+
const response = await fetch(mediaUrl);
|
|
1066
|
+
if (!response.ok) {
|
|
1067
|
+
throw new Error(`Failed to download generated ${meta.type}`);
|
|
1068
|
+
}
|
|
1069
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
1070
|
+
const contentType = response.headers.get('content-type') || '';
|
|
1071
|
+
// Determine extension from content-type
|
|
1072
|
+
let extension = 'bin';
|
|
1073
|
+
let mimeType = contentType.split(';')[0].trim();
|
|
1074
|
+
if (meta.type === 'image') {
|
|
1075
|
+
if (contentType.includes('png'))
|
|
1076
|
+
extension = 'png';
|
|
1077
|
+
else if (contentType.includes('jpeg') || contentType.includes('jpg'))
|
|
1078
|
+
extension = 'jpg';
|
|
1079
|
+
else if (contentType.includes('webp'))
|
|
1080
|
+
extension = 'webp';
|
|
1081
|
+
else
|
|
1082
|
+
extension = 'png';
|
|
1083
|
+
mimeType = mimeType || `image/${extension}`;
|
|
1084
|
+
}
|
|
1085
|
+
else if (meta.type === 'audio') {
|
|
1086
|
+
if (contentType.includes('mp3') || contentType.includes('mpeg'))
|
|
1087
|
+
extension = 'mp3';
|
|
1088
|
+
else if (contentType.includes('wav'))
|
|
1089
|
+
extension = 'wav';
|
|
1090
|
+
else if (contentType.includes('ogg'))
|
|
1091
|
+
extension = 'ogg';
|
|
1092
|
+
else
|
|
1093
|
+
extension = 'mp3';
|
|
1094
|
+
mimeType = mimeType || `audio/${extension}`;
|
|
1095
|
+
}
|
|
1096
|
+
else if (meta.type === 'video') {
|
|
1097
|
+
if (contentType.includes('mp4'))
|
|
1098
|
+
extension = 'mp4';
|
|
1099
|
+
else if (contentType.includes('webm'))
|
|
1100
|
+
extension = 'webm';
|
|
1101
|
+
else
|
|
1102
|
+
extension = 'mp4';
|
|
1103
|
+
mimeType = mimeType || `video/${extension}`;
|
|
1104
|
+
}
|
|
1105
|
+
const fileName = `generated-${meta.type}-${Date.now()}.${extension}`;
|
|
1106
|
+
// Upload via FormData to the resources endpoint (triggers thumbnail generation, etc.)
|
|
1107
|
+
const formData = new FormData();
|
|
1108
|
+
const blob = new Blob([buffer], { type: mimeType });
|
|
1109
|
+
formData.set('file', blob, fileName);
|
|
1110
|
+
// Build description with generation metadata
|
|
1111
|
+
const descParts = [];
|
|
1112
|
+
if (meta.description)
|
|
1113
|
+
descParts.push(meta.description);
|
|
1114
|
+
if (meta.prompt)
|
|
1115
|
+
descParts.push(`Generated with: "${meta.prompt}"`);
|
|
1116
|
+
if (meta.model)
|
|
1117
|
+
descParts.push(`Model: ${meta.model}`);
|
|
1118
|
+
const fullDesc = descParts.join(' | ');
|
|
1119
|
+
if (fullDesc)
|
|
1120
|
+
formData.set('description', fullDesc);
|
|
1121
|
+
// Build tags
|
|
1122
|
+
const tagParts = [];
|
|
1123
|
+
if (meta.tags)
|
|
1124
|
+
tagParts.push(meta.tags);
|
|
1125
|
+
tagParts.push('ai-generated', meta.type);
|
|
1126
|
+
formData.set('tags', tagParts.join(', '));
|
|
1127
|
+
if (meta.prompt)
|
|
1128
|
+
formData.set('generationPrompt', meta.prompt);
|
|
1129
|
+
if (meta.model)
|
|
1130
|
+
formData.set('generationModel', meta.model);
|
|
1131
|
+
const uploadResponse = await fetch(`${apiBase}/api/v1/resources`, {
|
|
1132
|
+
method: 'POST',
|
|
1133
|
+
body: formData,
|
|
1134
|
+
});
|
|
1135
|
+
const result = await uploadResponse.json();
|
|
1136
|
+
if (!result.success || !result.data) {
|
|
1137
|
+
throw new Error('Failed to save generated media to resources');
|
|
1138
|
+
}
|
|
1139
|
+
return result.data;
|
|
1140
|
+
}
|
|
820
1141
|
// ─── Helper Functions ────────────────────────────────────────────────────────
|
|
821
1142
|
function getContextBasePath() {
|
|
822
1143
|
let basePath;
|
|
@@ -842,7 +1163,7 @@ function getKnowledgeBasePath() {
|
|
|
842
1163
|
else {
|
|
843
1164
|
basePath = pathResolve(process.cwd(), '.nut');
|
|
844
1165
|
}
|
|
845
|
-
return join(basePath, '
|
|
1166
|
+
return join(basePath, 'knowledge');
|
|
846
1167
|
}
|
|
847
1168
|
function slugify(value) {
|
|
848
1169
|
const slug = value
|
|
@@ -4,6 +4,13 @@ export declare function GET(c: Context): Promise<(Response & import("hono").Type
|
|
|
4
4
|
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
5
5
|
error: string;
|
|
6
6
|
}, 404, "json">)>;
|
|
7
|
+
export declare function PUT(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
8
|
+
success: true;
|
|
9
|
+
chatId: string;
|
|
10
|
+
message: string;
|
|
11
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
12
|
+
error: string;
|
|
13
|
+
}, 500, "json">)>;
|
|
7
14
|
export declare function DELETE(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
8
15
|
success: true;
|
|
9
16
|
message: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile, unlink } from "fs/promises";
|
|
1
|
+
import { readFile, writeFile, unlink } from "fs/promises";
|
|
2
2
|
import { join, resolve } from "path";
|
|
3
3
|
function getChatsPath() {
|
|
4
4
|
let basePath;
|
|
@@ -30,6 +30,35 @@ export async function GET(c) {
|
|
|
30
30
|
return c.json({ error: "Chat not found" }, 404);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
export async function PUT(c) {
|
|
34
|
+
try {
|
|
35
|
+
const id = c.req.param('id');
|
|
36
|
+
const filePath = join(CHATS_DIR, `${id}.json`);
|
|
37
|
+
// Read existing chat
|
|
38
|
+
const existing = JSON.parse(await readFile(filePath, 'utf8'));
|
|
39
|
+
const body = await c.req.json();
|
|
40
|
+
const { messages, model, context, title } = body;
|
|
41
|
+
const now = new Date().toISOString();
|
|
42
|
+
const updated = {
|
|
43
|
+
...existing,
|
|
44
|
+
messages: messages ?? existing.messages,
|
|
45
|
+
model: model ?? existing.model,
|
|
46
|
+
context: context ?? existing.context,
|
|
47
|
+
title: title ?? existing.title,
|
|
48
|
+
updatedAt: now,
|
|
49
|
+
};
|
|
50
|
+
await writeFile(filePath, JSON.stringify(updated, null, 2));
|
|
51
|
+
return c.json({
|
|
52
|
+
success: true,
|
|
53
|
+
chatId: id,
|
|
54
|
+
message: "Chat updated successfully"
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error("Error updating chat:", error);
|
|
59
|
+
return c.json({ error: "Failed to update chat" }, 500);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
33
62
|
export async function DELETE(c) {
|
|
34
63
|
try {
|
|
35
64
|
const id = c.req.param('id');
|
|
@@ -2,12 +2,10 @@ import { Hono } from 'hono';
|
|
|
2
2
|
import project from './project/route.js';
|
|
3
3
|
import architecture from './architecture/route.js';
|
|
4
4
|
import role from './role/route.js';
|
|
5
|
-
import knowledge from './knowledge/index.js';
|
|
6
5
|
import images from './images/index.js';
|
|
7
6
|
const context = new Hono();
|
|
8
7
|
context.route('/project', project);
|
|
9
8
|
context.route('/architecture', architecture);
|
|
10
9
|
context.route('/role', role);
|
|
11
|
-
context.route('/knowledge', knowledge);
|
|
12
10
|
context.route('/images', images);
|
|
13
11
|
export default context;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './route.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './route.js';
|