synthos 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -1
- package/default-pages/app_builder.html +40 -0
- package/default-pages/app_builder.json +1 -0
- package/default-pages/json_tools.html +89 -159
- package/default-pages/json_tools.json +1 -0
- package/default-pages/my_notes.html +33 -0
- package/default-pages/my_notes.json +12 -0
- package/default-pages/neon_asteroids.html +77 -0
- package/default-pages/neon_asteroids.json +12 -0
- package/default-pages/sidebar_builder.html +49 -0
- package/default-pages/sidebar_builder.json +1 -0
- package/default-pages/solar_explorer.html +1956 -0
- package/default-pages/solar_explorer.json +12 -0
- package/default-pages/solar_tutorial.html +476 -0
- package/default-pages/solar_tutorial.json +1 -0
- package/default-pages/two-panel_builder.html +66 -0
- package/default-pages/two-panel_builder.json +1 -0
- package/dist/connectors/index.d.ts +3 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +6 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/registry.d.ts +3 -0
- package/dist/connectors/registry.d.ts.map +1 -0
- package/dist/connectors/registry.js +100 -0
- package/dist/connectors/registry.js.map +1 -0
- package/dist/connectors/types.d.ts +61 -0
- package/dist/connectors/types.d.ts.map +1 -0
- package/dist/connectors/types.js +3 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/files.d.ts +2 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +12 -1
- package/dist/files.js.map +1 -1
- package/dist/init.d.ts +8 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +155 -3
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts +11 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +281 -0
- package/dist/migrations.js.map +1 -0
- package/dist/models/index.d.ts +3 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +10 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models/providers.d.ts +7 -0
- package/dist/models/providers.d.ts.map +1 -0
- package/dist/models/providers.js +33 -0
- package/dist/models/providers.js.map +1 -0
- package/dist/models/types.d.ts +21 -0
- package/dist/models/types.d.ts.map +1 -0
- package/dist/models/types.js +3 -0
- package/dist/models/types.js.map +1 -0
- package/dist/pages.d.ts +21 -2
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +202 -23
- package/dist/pages.js.map +1 -1
- package/dist/scripts.js +2 -2
- package/dist/scripts.js.map +1 -1
- package/dist/service/createCompletePrompt.d.ts +3 -2
- package/dist/service/createCompletePrompt.d.ts.map +1 -1
- package/dist/service/createCompletePrompt.js +11 -16
- package/dist/service/createCompletePrompt.js.map +1 -1
- package/dist/service/debugLog.d.ts +11 -0
- package/dist/service/debugLog.d.ts.map +1 -0
- package/dist/service/debugLog.js +26 -0
- package/dist/service/debugLog.js.map +1 -0
- package/dist/service/modelInstructions.d.ts +7 -0
- package/dist/service/modelInstructions.d.ts.map +1 -0
- package/dist/service/modelInstructions.js +16 -0
- package/dist/service/modelInstructions.js.map +1 -0
- package/dist/service/requiresSettings.d.ts +2 -2
- package/dist/service/requiresSettings.d.ts.map +1 -1
- package/dist/service/requiresSettings.js.map +1 -1
- package/dist/service/server.d.ts.map +1 -1
- package/dist/service/server.js +15 -0
- package/dist/service/server.js.map +1 -1
- package/dist/service/transformPage.d.ts +81 -2
- package/dist/service/transformPage.d.ts.map +1 -1
- package/dist/service/transformPage.js +672 -82
- package/dist/service/transformPage.js.map +1 -1
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +579 -13
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.d.ts +4 -0
- package/dist/service/useConnectorRoutes.d.ts.map +1 -0
- package/dist/service/useConnectorRoutes.js +389 -0
- package/dist/service/useConnectorRoutes.js.map +1 -0
- package/dist/service/useDataRoutes.d.ts.map +1 -1
- package/dist/service/useDataRoutes.js +83 -70
- package/dist/service/useDataRoutes.js.map +1 -1
- package/dist/service/usePageRoutes.d.ts.map +1 -1
- package/dist/service/usePageRoutes.js +243 -38
- package/dist/service/usePageRoutes.js.map +1 -1
- package/dist/settings.d.ts +33 -4
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +108 -15
- package/dist/settings.js.map +1 -1
- package/dist/synthos-cli.d.ts.map +1 -1
- package/dist/synthos-cli.js +11 -1
- package/dist/synthos-cli.js.map +1 -1
- package/dist/themes.d.ts +9 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +64 -0
- package/dist/themes.js.map +1 -0
- package/package.json +5 -3
- package/required-pages/builder.html +74 -0
- package/required-pages/builder.json +1 -0
- package/required-pages/pages.html +169 -126
- package/required-pages/pages.json +1 -0
- package/required-pages/settings.html +812 -156
- package/required-pages/settings.json +1 -0
- package/required-pages/synthos_apis.html +272 -0
- package/required-pages/synthos_apis.json +1 -0
- package/required-pages/synthos_scripts.html +87 -0
- package/required-pages/synthos_scripts.json +1 -0
- package/src/connectors/index.ts +12 -0
- package/src/connectors/registry.ts +98 -0
- package/src/connectors/types.ts +68 -0
- package/src/files.ts +11 -0
- package/src/init.ts +151 -5
- package/src/migrations.ts +266 -0
- package/src/models/index.ts +2 -0
- package/src/models/providers.ts +33 -0
- package/src/models/types.ts +23 -0
- package/src/pages.ts +234 -26
- package/src/scripts.ts +2 -2
- package/src/service/createCompletePrompt.ts +14 -18
- package/src/service/debugLog.ts +17 -0
- package/src/service/modelInstructions.ts +14 -0
- package/src/service/requiresSettings.ts +3 -3
- package/src/service/server.ts +19 -2
- package/src/service/transformPage.ts +709 -88
- package/src/service/useApiRoutes.ts +632 -16
- package/src/service/useConnectorRoutes.ts +427 -0
- package/src/service/useDataRoutes.ts +87 -71
- package/src/service/usePageRoutes.ts +237 -44
- package/src/settings.ts +143 -20
- package/src/synthos-cli.ts +11 -1
- package/src/themes.ts +71 -0
- package/default-pages/[application].html +0 -95
- package/default-pages/[markdown].html +0 -271
- package/default-pages/[sidebar].html +0 -114
- package/default-pages/[split-application].html +0 -118
- package/default-pages/solar_system.html +0 -432
- package/default-pages/space_invaders.html +0 -617
- package/required-pages/apis.html +0 -362
- package/required-pages/home.html +0 -126
- package/required-pages/scripts.html +0 -350
|
@@ -1,38 +1,265 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.useApiRoutes = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
4
8
|
const pages_1 = require("../pages");
|
|
9
|
+
const files_1 = require("../files");
|
|
5
10
|
const settings_1 = require("../settings");
|
|
6
11
|
const createCompletePrompt_1 = require("./createCompletePrompt");
|
|
7
12
|
const generateImage_1 = require("./generateImage");
|
|
8
13
|
const agentm_core_1 = require("agentm-core");
|
|
9
14
|
const requiresSettings_1 = require("./requiresSettings");
|
|
10
15
|
const scripts_1 = require("../scripts");
|
|
16
|
+
const themes_1 = require("../themes");
|
|
17
|
+
const migrations_1 = require("../migrations");
|
|
18
|
+
const usePageRoutes_1 = require("./usePageRoutes");
|
|
19
|
+
const SERVICE_REGISTRY = [
|
|
20
|
+
{
|
|
21
|
+
id: 'brave-search',
|
|
22
|
+
name: 'Brave Search',
|
|
23
|
+
category: 'Search',
|
|
24
|
+
description: 'Web search powered by the Brave Search API. Provides real-time search results from the web.',
|
|
25
|
+
fields: [
|
|
26
|
+
{ name: 'apiKey', label: 'API Key', type: 'password' }
|
|
27
|
+
],
|
|
28
|
+
exclusive: 'search'
|
|
29
|
+
}
|
|
30
|
+
];
|
|
11
31
|
function useApiRoutes(config, app) {
|
|
12
32
|
// List pages
|
|
13
33
|
app.get('/api/pages', async (req, res) => {
|
|
14
34
|
const pages = await (0, pages_1.listPages)(config.pagesFolder, config.requiredPagesFolder);
|
|
15
35
|
res.json(pages);
|
|
16
36
|
});
|
|
37
|
+
// Get page metadata
|
|
38
|
+
app.get('/api/pages/:name', async (req, res) => {
|
|
39
|
+
try {
|
|
40
|
+
const { name } = req.params;
|
|
41
|
+
const metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, name, config.requiredPagesFolder);
|
|
42
|
+
if (metadata) {
|
|
43
|
+
res.json(metadata);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const defaults = {
|
|
47
|
+
title: '',
|
|
48
|
+
categories: [],
|
|
49
|
+
pinned: false,
|
|
50
|
+
showInAll: true,
|
|
51
|
+
createdDate: '',
|
|
52
|
+
lastModified: '',
|
|
53
|
+
pageVersion: 0,
|
|
54
|
+
mode: 'unlocked',
|
|
55
|
+
};
|
|
56
|
+
res.json(defaults);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
console.error(err);
|
|
61
|
+
res.status(500).send(err.message);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
// Save page metadata (merge semantics)
|
|
65
|
+
app.post('/api/pages/:name', async (req, res) => {
|
|
66
|
+
try {
|
|
67
|
+
const { name } = req.params;
|
|
68
|
+
const body = req.body;
|
|
69
|
+
// Validate provided fields only
|
|
70
|
+
if ('title' in body && typeof body.title !== 'string') {
|
|
71
|
+
res.status(400).json({ error: 'title must be a string' });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if ('categories' in body && !Array.isArray(body.categories)) {
|
|
75
|
+
res.status(400).json({ error: 'categories must be an array' });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if ('pinned' in body && typeof body.pinned !== 'boolean') {
|
|
79
|
+
res.status(400).json({ error: 'pinned must be a boolean' });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if ('mode' in body && body.mode !== 'unlocked' && body.mode !== 'locked') {
|
|
83
|
+
res.status(400).json({ error: 'mode must be "unlocked" or "locked"' });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if ('showInAll' in body && typeof body.showInAll !== 'boolean') {
|
|
87
|
+
res.status(400).json({ error: 'showInAll must be a boolean' });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Load existing metadata (or defaults)
|
|
91
|
+
const existing = await (0, pages_1.loadPageMetadata)(config.pagesFolder, name, config.requiredPagesFolder);
|
|
92
|
+
const metadata = {
|
|
93
|
+
title: existing?.title ?? '',
|
|
94
|
+
categories: existing?.categories ?? [],
|
|
95
|
+
pinned: existing?.pinned ?? false,
|
|
96
|
+
showInAll: existing?.showInAll ?? true,
|
|
97
|
+
createdDate: existing?.createdDate ?? '',
|
|
98
|
+
lastModified: existing?.lastModified ?? '',
|
|
99
|
+
pageVersion: existing?.pageVersion ?? 0,
|
|
100
|
+
mode: existing?.mode ?? 'unlocked',
|
|
101
|
+
};
|
|
102
|
+
// Overlay provided fields
|
|
103
|
+
if ('title' in body)
|
|
104
|
+
metadata.title = body.title;
|
|
105
|
+
if ('categories' in body)
|
|
106
|
+
metadata.categories = body.categories;
|
|
107
|
+
if ('pinned' in body)
|
|
108
|
+
metadata.pinned = body.pinned;
|
|
109
|
+
if ('showInAll' in body)
|
|
110
|
+
metadata.showInAll = body.showInAll;
|
|
111
|
+
if ('mode' in body)
|
|
112
|
+
metadata.mode = body.mode;
|
|
113
|
+
// Auto-set lastModified
|
|
114
|
+
metadata.lastModified = new Date().toISOString();
|
|
115
|
+
// Promote required page to user folder if being unlocked/designed
|
|
116
|
+
if (metadata.mode !== 'locked') {
|
|
117
|
+
const userPagePath = path_1.default.join(config.pagesFolder, 'pages', name, 'page.html');
|
|
118
|
+
if (!(await (0, files_1.checkIfExists)(userPagePath))) {
|
|
119
|
+
const html = await (0, pages_1.loadPageState)(config.requiredPagesFolder, name, false);
|
|
120
|
+
if (html) {
|
|
121
|
+
await (0, pages_1.savePageState)(config.pagesFolder, name, html);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
await (0, pages_1.savePageMetadata)(config.pagesFolder, name, metadata);
|
|
126
|
+
res.json(metadata);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
console.error(err);
|
|
130
|
+
res.status(500).send(err.message);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
// Pin/unpin a page
|
|
134
|
+
app.post('/api/pages/:name/pin', async (req, res) => {
|
|
135
|
+
try {
|
|
136
|
+
const { name } = req.params;
|
|
137
|
+
const { pinned } = req.body;
|
|
138
|
+
if (typeof pinned !== 'boolean') {
|
|
139
|
+
res.status(400).json({ error: 'pinned must be a boolean' });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// Load existing metadata (user override → fallback .json → defaults)
|
|
143
|
+
let metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, name, config.requiredPagesFolder);
|
|
144
|
+
if (!metadata) {
|
|
145
|
+
metadata = {
|
|
146
|
+
title: '',
|
|
147
|
+
categories: [],
|
|
148
|
+
pinned: false,
|
|
149
|
+
showInAll: true,
|
|
150
|
+
createdDate: '',
|
|
151
|
+
lastModified: '',
|
|
152
|
+
pageVersion: 0,
|
|
153
|
+
mode: 'unlocked',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
metadata.pinned = pinned;
|
|
157
|
+
await (0, pages_1.savePageMetadata)(config.pagesFolder, name, metadata);
|
|
158
|
+
res.json(metadata);
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
console.error(err);
|
|
162
|
+
res.status(500).send(err.message);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
// Delete a page
|
|
166
|
+
app.delete('/api/pages/:name', async (req, res) => {
|
|
167
|
+
try {
|
|
168
|
+
const { name } = req.params;
|
|
169
|
+
// Cannot delete required pages
|
|
170
|
+
if (pages_1.REQUIRED_PAGES.includes(name)) {
|
|
171
|
+
res.status(400).json({ error: `Cannot delete required page "${name}"` });
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// Check if page exists (folder-based or legacy flat file)
|
|
175
|
+
const folderPath = path_1.default.join(config.pagesFolder, 'pages', name, 'page.html');
|
|
176
|
+
const flatPath = path_1.default.join(config.pagesFolder, `${name}.html`);
|
|
177
|
+
const exists = await (0, files_1.checkIfExists)(folderPath) || await (0, files_1.checkIfExists)(flatPath);
|
|
178
|
+
if (!exists) {
|
|
179
|
+
res.status(404).json({ error: `Page "${name}" not found` });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
await (0, pages_1.deletePage)(config.pagesFolder, name);
|
|
183
|
+
res.json({ deleted: true });
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
console.error(err);
|
|
187
|
+
res.status(500).send(err.message);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
// Copy a page to a new name
|
|
191
|
+
app.post('/api/pages/:name/copy', async (req, res) => {
|
|
192
|
+
try {
|
|
193
|
+
const sourceName = req.params.name;
|
|
194
|
+
const { name: targetName, title, categories } = req.body;
|
|
195
|
+
// Validate target name
|
|
196
|
+
if (!targetName || typeof targetName !== 'string') {
|
|
197
|
+
res.status(400).json({ error: 'name is required' });
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(targetName)) {
|
|
201
|
+
res.status(400).json({ error: 'name can only contain letters, numbers, hyphens, and underscores' });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
// Can't copy over yourself
|
|
205
|
+
if (targetName === sourceName) {
|
|
206
|
+
res.status(400).json({ error: 'Cannot copy a page to itself' });
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// Check source exists (user pages → required pages)
|
|
210
|
+
const sourceFolderPath = path_1.default.join(config.pagesFolder, 'pages', sourceName, 'page.html');
|
|
211
|
+
const sourceFlatPath = path_1.default.join(config.pagesFolder, `${sourceName}.html`);
|
|
212
|
+
const sourceRequiredPath = path_1.default.join(config.requiredPagesFolder, `${sourceName}.html`);
|
|
213
|
+
const sourceExists = await (0, files_1.checkIfExists)(sourceFolderPath)
|
|
214
|
+
|| await (0, files_1.checkIfExists)(sourceFlatPath)
|
|
215
|
+
|| await (0, files_1.checkIfExists)(sourceRequiredPath);
|
|
216
|
+
if (!sourceExists) {
|
|
217
|
+
res.status(404).json({ error: `Source page "${sourceName}" not found` });
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// Check target doesn't already exist
|
|
221
|
+
const targetFolderPath = path_1.default.join(config.pagesFolder, 'pages', targetName, 'page.html');
|
|
222
|
+
const targetFlatPath = path_1.default.join(config.pagesFolder, `${targetName}.html`);
|
|
223
|
+
if (await (0, files_1.checkIfExists)(targetFolderPath) || await (0, files_1.checkIfExists)(targetFlatPath)) {
|
|
224
|
+
res.status(409).json({ error: `Page "${targetName}" already exists` });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
await (0, pages_1.copyPage)(config.pagesFolder, sourceName, targetName, typeof title === 'string' ? title : '', Array.isArray(categories) ? categories : [], config.requiredPagesFolder);
|
|
228
|
+
// Return the new page metadata
|
|
229
|
+
const metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, targetName);
|
|
230
|
+
res.status(201).json({ name: targetName, ...metadata });
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
console.error(err);
|
|
234
|
+
res.status(500).send(err.message);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
17
237
|
// Define a route to return settings
|
|
18
238
|
app.get('/api/settings', async (req, res) => {
|
|
19
239
|
const settings = await (0, settings_1.loadSettings)(config.pagesFolder);
|
|
20
|
-
|
|
240
|
+
const providers = createCompletePrompt_1.PROVIDERS.map(p => ({ name: p.name, builderModels: p.builderModels, chatModels: p.chatModels }));
|
|
241
|
+
res.json({ ...settings, providers });
|
|
21
242
|
});
|
|
22
243
|
// Define a route to save settings
|
|
23
244
|
app.post('/api/settings', async (req, res) => {
|
|
24
245
|
try {
|
|
25
|
-
//
|
|
246
|
+
// Coerce non-string values inside models array
|
|
26
247
|
const settings = req.body;
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
248
|
+
if (Array.isArray(settings.models)) {
|
|
249
|
+
for (const entry of settings.models) {
|
|
250
|
+
if (entry.configuration) {
|
|
251
|
+
if (typeof entry.configuration.maxTokens === 'string') {
|
|
252
|
+
entry.configuration.maxTokens = parseInt(entry.configuration.maxTokens);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (typeof entry.logCompletions === 'string') {
|
|
256
|
+
entry.logCompletions = entry.logCompletions === 'true';
|
|
257
|
+
}
|
|
258
|
+
}
|
|
32
259
|
}
|
|
33
260
|
// Save settings
|
|
34
261
|
await (0, settings_1.saveSettings)(config.pagesFolder, settings);
|
|
35
|
-
res.redirect('/
|
|
262
|
+
res.redirect('/builder');
|
|
36
263
|
}
|
|
37
264
|
catch (err) {
|
|
38
265
|
console.error(err);
|
|
@@ -43,9 +270,10 @@ function useApiRoutes(config, app) {
|
|
|
43
270
|
app.post('/api/generate/image', async (req, res) => {
|
|
44
271
|
await (0, requiresSettings_1.requiresSettings)(res, config.pagesFolder, async (settings) => {
|
|
45
272
|
const { prompt, shape, style } = req.body;
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
273
|
+
const builder = (0, settings_1.getModelEntry)(settings, 'builder');
|
|
274
|
+
const { configuration, imageQuality, provider } = builder;
|
|
275
|
+
const response = provider === 'OpenAI' ?
|
|
276
|
+
await (0, generateImage_1.generateImage)({ apiKey: configuration.apiKey, prompt, shape, quality: imageQuality, style }) :
|
|
49
277
|
await (0, generateImage_1.generateDefaultImage)();
|
|
50
278
|
if (response.completed) {
|
|
51
279
|
res.json(response.value);
|
|
@@ -59,8 +287,8 @@ function useApiRoutes(config, app) {
|
|
|
59
287
|
app.post('/api/generate/completion', async (req, res) => {
|
|
60
288
|
await (0, requiresSettings_1.requiresSettings)(res, config.pagesFolder, async (settings) => {
|
|
61
289
|
const { prompt, temperature } = req.body;
|
|
62
|
-
const
|
|
63
|
-
const completePrompt = await (0, createCompletePrompt_1.createCompletePrompt)(config.pagesFolder, req.body.model);
|
|
290
|
+
const maxTokens = (0, settings_1.getModelEntry)(settings, 'chat').configuration.maxTokens;
|
|
291
|
+
const completePrompt = await (0, createCompletePrompt_1.createCompletePrompt)(config.pagesFolder, 'chat', req.body.model);
|
|
64
292
|
const response = await (0, agentm_core_1.chainOfThought)({ question: prompt, temperature, maxTokens, completePrompt });
|
|
65
293
|
if (response.completed) {
|
|
66
294
|
res.json(response.value ?? {});
|
|
@@ -71,6 +299,65 @@ function useApiRoutes(config, app) {
|
|
|
71
299
|
}
|
|
72
300
|
});
|
|
73
301
|
});
|
|
302
|
+
// Brainstorm endpoint
|
|
303
|
+
app.post('/api/brainstorm', async (req, res) => {
|
|
304
|
+
await (0, requiresSettings_1.requiresSettings)(res, config.pagesFolder, async (settings) => {
|
|
305
|
+
const { context, messages } = req.body;
|
|
306
|
+
const maxTokens = (0, settings_1.getModelEntry)(settings, 'chat').configuration.maxTokens;
|
|
307
|
+
const completePrompt = await (0, createCompletePrompt_1.createCompletePrompt)(config.pagesFolder, 'chat');
|
|
308
|
+
const system = {
|
|
309
|
+
role: 'system',
|
|
310
|
+
content: `You are a creative brainstorming assistant for SynthOS, a tool that builds web pages through conversation. The user is brainstorming — exploring ideas before building. Be concise, creative, and collaborative. Suggest concrete approaches when you can.
|
|
311
|
+
|
|
312
|
+
You MUST return your response as a JSON object with exactly these fields:
|
|
313
|
+
{
|
|
314
|
+
"response": "Your conversational reply — explanations, options, suggestions. Markdown OK.",
|
|
315
|
+
"prompt": "A clean, actionable instruction ready to paste into SynthOS chat to build what was discussed. Update this each exchange to reflect the latest brainstorm state.",
|
|
316
|
+
"suggestions": ["Short clickable option A", "Short clickable option B", "Short clickable option C"]
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
suggestions — 2-4 short phrases the user can click to continue the conversation. These are next-step options: directions to explore, questions to answer, or choices to make. Keep each under 60 characters. Always provide suggestions.
|
|
320
|
+
|
|
321
|
+
Return ONLY the JSON object. No markdown fences.
|
|
322
|
+
|
|
323
|
+
<CONTEXT>
|
|
324
|
+
${context}
|
|
325
|
+
</CONTEXT>`
|
|
326
|
+
};
|
|
327
|
+
// Format multi-turn conversation into a single prompt
|
|
328
|
+
const formatted = messages.map(m => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`).join('\n\n');
|
|
329
|
+
const prompt = { role: 'user', content: formatted };
|
|
330
|
+
const result = await completePrompt({ prompt, system, maxTokens, jsonMode: true });
|
|
331
|
+
if (result.completed) {
|
|
332
|
+
let response = result.value || '';
|
|
333
|
+
let brainstormPrompt = '';
|
|
334
|
+
let suggestions = [];
|
|
335
|
+
// jsonMode returns an already-parsed object from agentm-core
|
|
336
|
+
const parsed = (typeof result.value === 'object' && result.value !== null)
|
|
337
|
+
? result.value
|
|
338
|
+
: (() => { try {
|
|
339
|
+
return JSON.parse(result.value);
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
return null;
|
|
343
|
+
} })();
|
|
344
|
+
if (parsed) {
|
|
345
|
+
if (typeof parsed.response === 'string')
|
|
346
|
+
response = parsed.response;
|
|
347
|
+
if (typeof parsed.prompt === 'string')
|
|
348
|
+
brainstormPrompt = parsed.prompt;
|
|
349
|
+
if (Array.isArray(parsed.suggestions)) {
|
|
350
|
+
suggestions = parsed.suggestions.filter((s) => typeof s === 'string');
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
res.json({ response, prompt: brainstormPrompt, suggestions });
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
console.error(result.error);
|
|
357
|
+
res.status(500).send(result.error?.message);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
});
|
|
74
361
|
// Define a route for running configured scripts
|
|
75
362
|
app.post('/api/scripts/:id', async (req, res) => {
|
|
76
363
|
await (0, requiresSettings_1.requiresSettings)(res, config.pagesFolder, async (settings) => {
|
|
@@ -90,6 +377,285 @@ function useApiRoutes(config, app) {
|
|
|
90
377
|
}
|
|
91
378
|
});
|
|
92
379
|
});
|
|
380
|
+
// Return theme info as a self-executing JS script
|
|
381
|
+
app.get('/api/theme-info.js', async (req, res) => {
|
|
382
|
+
try {
|
|
383
|
+
const settings = await (0, settings_1.loadSettings)(config.pagesFolder);
|
|
384
|
+
const themeName = settings.theme ?? 'nebula-dusk';
|
|
385
|
+
const info = await (0, themes_1.loadThemeInfo)(themeName, config);
|
|
386
|
+
if (!info) {
|
|
387
|
+
res.status(404).send(`// Theme info for "${themeName}" not found`);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const js = `window.themeInfo=${JSON.stringify(info)};document.documentElement.classList.add(window.themeInfo.mode+"-mode");`;
|
|
391
|
+
res.set('Content-Type', 'application/javascript');
|
|
392
|
+
res.send(js);
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
console.error(err);
|
|
396
|
+
res.status(500).send(`// ${err.message}`);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
// Return page info as a self-executing JS script
|
|
400
|
+
app.get('/api/page-info.js', async (req, res) => {
|
|
401
|
+
try {
|
|
402
|
+
const page = req.query.page;
|
|
403
|
+
if (!page) {
|
|
404
|
+
res.status(400).send('// Missing page query parameter');
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, page, config.requiredPagesFolder);
|
|
408
|
+
const mode = metadata?.mode ?? 'unlocked';
|
|
409
|
+
const title = metadata?.title ?? '';
|
|
410
|
+
const categories = metadata?.categories ?? [];
|
|
411
|
+
const info = JSON.stringify({ name: page, mode, latestPageVersion: pages_1.PAGE_VERSION, title, categories });
|
|
412
|
+
const js = [
|
|
413
|
+
`window.pageInfo=${info};`,
|
|
414
|
+
`if(window.pageInfo.mode==="locked"){`,
|
|
415
|
+
`document.addEventListener("DOMContentLoaded",function(){`,
|
|
416
|
+
`var f=document.getElementById("chatForm");if(f)f.style.display="none";`,
|
|
417
|
+
`var s=document.getElementById("saveLink");if(s)s.textContent="Copy";`,
|
|
418
|
+
`var r=document.getElementById("resetLink");if(r){`,
|
|
419
|
+
`var c=r.cloneNode(true);c.textContent="Reload";`,
|
|
420
|
+
`c.addEventListener("click",function(e){e.preventDefault();window.location.href=window.location.pathname;});`,
|
|
421
|
+
`r.parentNode.replaceChild(c,r);}`,
|
|
422
|
+
`});`,
|
|
423
|
+
`}`,
|
|
424
|
+
].join('');
|
|
425
|
+
res.set('Content-Type', 'application/javascript');
|
|
426
|
+
res.set('Cache-Control', 'no-store');
|
|
427
|
+
res.send(js);
|
|
428
|
+
}
|
|
429
|
+
catch (err) {
|
|
430
|
+
console.error(err);
|
|
431
|
+
res.status(500).send(`// ${err.message}`);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
// Return the current theme as CSS
|
|
435
|
+
app.get('/api/theme.css', async (req, res) => {
|
|
436
|
+
try {
|
|
437
|
+
const settings = await (0, settings_1.loadSettings)(config.pagesFolder);
|
|
438
|
+
const themeName = settings.theme ?? 'nebula-dusk';
|
|
439
|
+
const css = await (0, themes_1.loadTheme)(themeName, config);
|
|
440
|
+
if (!css) {
|
|
441
|
+
res.status(404).send(`Theme "${themeName}" not found`);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
res.set('Content-Type', 'text/css');
|
|
445
|
+
res.send(css);
|
|
446
|
+
}
|
|
447
|
+
catch (err) {
|
|
448
|
+
console.error(err);
|
|
449
|
+
res.status(500).send(err.message);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
// List available themes
|
|
453
|
+
app.get('/api/themes', async (req, res) => {
|
|
454
|
+
try {
|
|
455
|
+
const themes = await (0, themes_1.listThemes)(config);
|
|
456
|
+
res.json(themes);
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
console.error(err);
|
|
460
|
+
res.status(500).send(err.message);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
// Return a versioned page script
|
|
464
|
+
app.get('/api/page-script.js', async (req, res) => {
|
|
465
|
+
try {
|
|
466
|
+
const v = parseInt(req.query.v, 10);
|
|
467
|
+
if (isNaN(v) || v < 1) {
|
|
468
|
+
res.status(400).send('// Invalid version parameter');
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const scriptPath = path_1.default.join(config.pageScriptsFolder, `page-v${v}.js`);
|
|
472
|
+
if (!(await (0, files_1.checkIfExists)(scriptPath))) {
|
|
473
|
+
res.status(404).send(`// page-v${v}.js not found`);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const js = await (0, files_1.loadFile)(scriptPath);
|
|
477
|
+
res.set('Content-Type', 'application/javascript');
|
|
478
|
+
res.set('Cache-Control', 'public, max-age=3600');
|
|
479
|
+
res.send(js);
|
|
480
|
+
}
|
|
481
|
+
catch (err) {
|
|
482
|
+
console.error(err);
|
|
483
|
+
res.status(500).send(`// ${err.message}`);
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
// Return versioned page helpers
|
|
487
|
+
app.get('/api/page-helpers.js', async (req, res) => {
|
|
488
|
+
try {
|
|
489
|
+
const v = parseInt(req.query.v, 10);
|
|
490
|
+
if (isNaN(v) || v < 1) {
|
|
491
|
+
res.status(400).send('// Invalid version parameter');
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const scriptPath = path_1.default.join(config.pageScriptsFolder, `helpers-v${v}.js`);
|
|
495
|
+
if (!(await (0, files_1.checkIfExists)(scriptPath))) {
|
|
496
|
+
res.status(404).send(`// helpers-v${v}.js not found`);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const js = await (0, files_1.loadFile)(scriptPath);
|
|
500
|
+
res.set('Content-Type', 'application/javascript');
|
|
501
|
+
res.set('Cache-Control', 'public, max-age=3600');
|
|
502
|
+
res.send(js);
|
|
503
|
+
}
|
|
504
|
+
catch (err) {
|
|
505
|
+
console.error(err);
|
|
506
|
+
res.status(500).send(`// ${err.message}`);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
// -----------------------------------------------------------------------
|
|
510
|
+
// Services
|
|
511
|
+
// -----------------------------------------------------------------------
|
|
512
|
+
// Return the service registry (available service definitions)
|
|
513
|
+
app.get('/api/services/registry', (_req, res) => {
|
|
514
|
+
res.json(SERVICE_REGISTRY);
|
|
515
|
+
});
|
|
516
|
+
// Return user's configured services (API keys masked)
|
|
517
|
+
app.get('/api/services', async (_req, res) => {
|
|
518
|
+
try {
|
|
519
|
+
const settings = await (0, settings_1.loadSettings)(config.pagesFolder);
|
|
520
|
+
const services = settings.services ?? {};
|
|
521
|
+
const masked = {};
|
|
522
|
+
for (const [id, cfg] of Object.entries(services)) {
|
|
523
|
+
masked[id] = {
|
|
524
|
+
enabled: cfg.enabled,
|
|
525
|
+
hasKey: typeof cfg.apiKey === 'string' && cfg.apiKey.length > 0
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
res.json(masked);
|
|
529
|
+
}
|
|
530
|
+
catch (err) {
|
|
531
|
+
console.error(err);
|
|
532
|
+
res.status(500).send(err.message);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
// Save services config (enforces exclusive groups)
|
|
536
|
+
app.post('/api/services', async (req, res) => {
|
|
537
|
+
try {
|
|
538
|
+
const incoming = req.body;
|
|
539
|
+
const settings = await (0, settings_1.loadSettings)(config.pagesFolder);
|
|
540
|
+
const existing = settings.services ?? {};
|
|
541
|
+
// Build merged config — empty apiKey means "keep existing"
|
|
542
|
+
const merged = {};
|
|
543
|
+
for (const [id, cfg] of Object.entries(incoming)) {
|
|
544
|
+
const apiKey = (cfg.apiKey && cfg.apiKey.length > 0) ? cfg.apiKey : (existing[id]?.apiKey ?? '');
|
|
545
|
+
merged[id] = { apiKey, enabled: cfg.enabled };
|
|
546
|
+
}
|
|
547
|
+
// Enforce exclusive groups: only one enabled per group
|
|
548
|
+
for (const def of SERVICE_REGISTRY) {
|
|
549
|
+
if (!def.exclusive)
|
|
550
|
+
continue;
|
|
551
|
+
if (!merged[def.id]?.enabled)
|
|
552
|
+
continue;
|
|
553
|
+
for (const other of SERVICE_REGISTRY) {
|
|
554
|
+
if (other.id !== def.id && other.exclusive === def.exclusive && merged[other.id]) {
|
|
555
|
+
merged[other.id].enabled = false;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
await (0, settings_1.saveSettings)(config.pagesFolder, { services: merged });
|
|
560
|
+
res.json({ saved: true });
|
|
561
|
+
}
|
|
562
|
+
catch (err) {
|
|
563
|
+
console.error(err);
|
|
564
|
+
res.status(500).send(err.message);
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
// -----------------------------------------------------------------------
|
|
568
|
+
// Web Search (Brave Search API)
|
|
569
|
+
// -----------------------------------------------------------------------
|
|
570
|
+
app.post('/api/search/web', async (req, res) => {
|
|
571
|
+
try {
|
|
572
|
+
const { query, count, country, freshness } = req.body;
|
|
573
|
+
if (!query || typeof query !== 'string') {
|
|
574
|
+
res.status(400).json({ error: 'query is required' });
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const settings = await (0, settings_1.loadSettings)(config.pagesFolder);
|
|
578
|
+
const braveConfig = settings.connectors?.['brave-search'] ?? settings.services?.['brave-search'];
|
|
579
|
+
if (!braveConfig || !braveConfig.enabled || !braveConfig.apiKey) {
|
|
580
|
+
res.status(400).json({ error: 'Brave Search is not configured or not enabled. Add your API key in Settings > Services.' });
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const params = new URLSearchParams({ q: query });
|
|
584
|
+
if (count)
|
|
585
|
+
params.set('count', String(Math.min(Number(count) || 5, 20)));
|
|
586
|
+
if (country)
|
|
587
|
+
params.set('country', country);
|
|
588
|
+
if (freshness)
|
|
589
|
+
params.set('freshness', freshness);
|
|
590
|
+
const response = await fetch(`https://api.search.brave.com/res/v1/web/search?${params.toString()}`, {
|
|
591
|
+
headers: {
|
|
592
|
+
'Accept': 'application/json',
|
|
593
|
+
'Accept-Encoding': 'gzip',
|
|
594
|
+
'X-Subscription-Token': braveConfig.apiKey
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
if (!response.ok) {
|
|
598
|
+
const text = await response.text();
|
|
599
|
+
res.status(response.status).json({ error: `Brave Search API error: ${text}` });
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const data = await response.json();
|
|
603
|
+
const results = (data.web?.results ?? []).map(r => ({
|
|
604
|
+
title: r.title,
|
|
605
|
+
url: r.url,
|
|
606
|
+
description: r.description
|
|
607
|
+
}));
|
|
608
|
+
res.json({ results });
|
|
609
|
+
}
|
|
610
|
+
catch (err) {
|
|
611
|
+
console.error(err);
|
|
612
|
+
res.status(500).json({ error: err.message });
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
// Upgrade a page to the latest version
|
|
616
|
+
app.post('/api/pages/:name/upgrade', async (req, res) => {
|
|
617
|
+
try {
|
|
618
|
+
const { name } = req.params;
|
|
619
|
+
// Load current metadata
|
|
620
|
+
const metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, name, config.requiredPagesFolder);
|
|
621
|
+
if (!metadata) {
|
|
622
|
+
res.status(404).json({ error: `Page "${name}" not found` });
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const currentVersion = metadata.pageVersion;
|
|
626
|
+
if (currentVersion >= pages_1.PAGE_VERSION) {
|
|
627
|
+
res.json({ upgraded: false, currentVersion });
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
// Load the page HTML
|
|
631
|
+
const html = await (0, usePageRoutes_1.loadPageWithFallback)(name, config, false);
|
|
632
|
+
if (!html) {
|
|
633
|
+
res.status(404).json({ error: `Page HTML for "${name}" not found` });
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
// Run LLM-based migration
|
|
637
|
+
const completePrompt = await (0, createCompletePrompt_1.createCompletePrompt)(config.pagesFolder, 'builder');
|
|
638
|
+
const migratedHtml = await (0, migrations_1.migratePage)(html, currentVersion, pages_1.PAGE_VERSION, completePrompt);
|
|
639
|
+
// Save upgraded HTML to v2 folder structure
|
|
640
|
+
await (0, pages_1.savePageState)(config.pagesFolder, name, migratedHtml);
|
|
641
|
+
// Move legacy flat file to .migrated folder instead of deleting
|
|
642
|
+
const flatPath = path_1.default.join(config.pagesFolder, `${name}.html`);
|
|
643
|
+
if (await (0, files_1.checkIfExists)(flatPath)) {
|
|
644
|
+
const migratedFolder = path_1.default.join(config.pagesFolder, '.migrated');
|
|
645
|
+
await (0, files_1.copyFile)(flatPath, migratedFolder);
|
|
646
|
+
await (0, files_1.deleteFile)(flatPath);
|
|
647
|
+
}
|
|
648
|
+
// Update metadata
|
|
649
|
+
metadata.pageVersion = pages_1.PAGE_VERSION;
|
|
650
|
+
metadata.lastModified = new Date().toISOString();
|
|
651
|
+
await (0, pages_1.savePageMetadata)(config.pagesFolder, name, metadata);
|
|
652
|
+
res.json({ upgraded: true, fromVersion: currentVersion, toVersion: pages_1.PAGE_VERSION });
|
|
653
|
+
}
|
|
654
|
+
catch (err) {
|
|
655
|
+
console.error(err);
|
|
656
|
+
res.status(500).json({ error: err.message });
|
|
657
|
+
}
|
|
658
|
+
});
|
|
93
659
|
}
|
|
94
660
|
exports.useApiRoutes = useApiRoutes;
|
|
95
661
|
//# sourceMappingURL=useApiRoutes.js.map
|