synthos 0.10.0 → 0.11.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 +5 -5
- package/default-pages/elevenlabs_effects_studio/chat-history.json +1 -0
- package/default-pages/elevenlabs_effects_studio/page.html +1345 -1363
- package/default-pages/elevenlabs_effects_studio/page.json +13 -11
- package/default-pages/elevenlabs_voice_studio/chat-history.json +1 -0
- package/default-pages/elevenlabs_voice_studio/page.html +782 -801
- package/default-pages/elevenlabs_voice_studio/page.json +13 -11
- package/default-pages/json_tools/chat-history.json +1 -0
- package/default-pages/json_tools/page.html +70 -90
- package/default-pages/json_tools/page.json +12 -10
- package/default-pages/my_notes/chat-history.json +1 -0
- package/default-pages/my_notes/page.html +115 -131
- package/default-pages/my_notes/page.json +14 -12
- package/default-pages/neon_asteroids/chat-history.json +1 -0
- package/default-pages/neon_asteroids/page.html +1777 -1803
- package/default-pages/neon_asteroids/page.json +14 -12
- package/default-pages/oregon_trail/chat-history.json +1 -0
- package/default-pages/oregon_trail/page.html +290 -307
- package/default-pages/oregon_trail/page.json +14 -12
- package/default-pages/solar_explorer/chat-history.json +1 -0
- package/default-pages/solar_explorer/page.html +1929 -1951
- package/default-pages/solar_explorer/page.json +14 -12
- package/default-pages/solar_tutorial/chat-history.json +1 -0
- package/default-pages/solar_tutorial/page.html +464 -478
- package/default-pages/solar_tutorial/page.json +12 -10
- package/default-pages/us_map/chat-history.json +1 -0
- package/default-pages/us_map/page.html +170 -193
- package/default-pages/us_map/page.json +14 -12
- package/default-pages/us_map/page.light.png +0 -0
- package/default-pages/us_map_1850/chat-history.json +1 -0
- package/default-pages/us_map_1850/page.html +302 -326
- package/default-pages/us_map_1850/page.json +14 -12
- package/default-pages/western_cities_1850/chat-history.json +1 -0
- package/default-pages/western_cities_1850/page.html +503 -527
- package/default-pages/western_cities_1850/page.json +14 -12
- package/default-themes/aurora-dawn.v3.css +15 -14
- package/default-themes/aurora-dusk.v3.css +26 -26
- package/default-themes/cosmos-dawn.v3.css +15 -14
- package/default-themes/cosmos-dusk.v3.css +26 -26
- package/default-themes/elemental-dawn.v3.css +200 -0
- package/default-themes/nebula-dawn.v3.css +15 -14
- package/default-themes/nebula-dusk.v3.css +24 -24
- package/default-themes/solar-flare-dawn.v3.css +15 -14
- package/default-themes/solar-flare-dusk.v3.css +26 -26
- package/dist/builders/anthropic.d.ts +26 -2
- package/dist/builders/anthropic.d.ts.map +1 -1
- package/dist/builders/anthropic.js +132 -31
- package/dist/builders/anthropic.js.map +1 -1
- package/dist/builders/claudecode.d.ts +13 -0
- package/dist/builders/claudecode.d.ts.map +1 -0
- package/dist/builders/claudecode.js +253 -0
- package/dist/builders/claudecode.js.map +1 -0
- package/dist/builders/index.d.ts +2 -1
- package/dist/builders/index.d.ts.map +1 -1
- package/dist/builders/index.js +8 -1
- package/dist/builders/index.js.map +1 -1
- package/dist/builders/openai.js +2 -1
- package/dist/builders/openai.js.map +1 -1
- package/dist/builders/types.d.ts +31 -7
- package/dist/builders/types.d.ts.map +1 -1
- package/dist/builders/types.js +60 -28
- package/dist/builders/types.js.map +1 -1
- package/dist/connectors/types.d.ts +8 -0
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +13 -6
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +161 -14
- package/dist/migrations.js.map +1 -1
- package/dist/models/anthropic.d.ts +1 -0
- package/dist/models/anthropic.d.ts.map +1 -1
- package/dist/models/anthropic.js +129 -29
- package/dist/models/anthropic.js.map +1 -1
- package/dist/models/chainOfThought.d.ts.map +1 -1
- package/dist/models/chainOfThought.js +32 -19
- package/dist/models/chainOfThought.js.map +1 -1
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +2 -1
- package/dist/models/index.js.map +1 -1
- package/dist/models/providers.d.ts +1 -0
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/models/providers.js +12 -4
- package/dist/models/providers.js.map +1 -1
- package/dist/models/types.d.ts +15 -1
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js.map +1 -1
- package/dist/pages.d.ts +57 -8
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +258 -45
- package/dist/pages.js.map +1 -1
- package/dist/service/createCompletePrompt.d.ts.map +1 -1
- package/dist/service/createCompletePrompt.js +5 -0
- package/dist/service/createCompletePrompt.js.map +1 -1
- package/dist/service/mediaCache.d.ts +36 -0
- package/dist/service/mediaCache.d.ts.map +1 -0
- package/dist/service/mediaCache.js +182 -0
- package/dist/service/mediaCache.js.map +1 -0
- package/dist/service/pageValidator.d.ts +25 -0
- package/dist/service/pageValidator.d.ts.map +1 -0
- package/dist/service/pageValidator.js +315 -0
- package/dist/service/pageValidator.js.map +1 -0
- package/dist/service/server.d.ts.map +1 -1
- package/dist/service/server.js +4 -0
- package/dist/service/server.js.map +1 -1
- package/dist/service/sharedTableSchema.d.ts +73 -0
- package/dist/service/sharedTableSchema.d.ts.map +1 -0
- package/dist/service/sharedTableSchema.js +206 -0
- package/dist/service/sharedTableSchema.js.map +1 -0
- package/dist/service/transformPage.d.ts +49 -11
- package/dist/service/transformPage.d.ts.map +1 -1
- package/dist/service/transformPage.js +354 -241
- package/dist/service/transformPage.js.map +1 -1
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +288 -34
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.d.ts.map +1 -1
- package/dist/service/useConnectorRoutes.js +170 -32
- package/dist/service/useConnectorRoutes.js.map +1 -1
- package/dist/service/useDataRoutes.d.ts.map +1 -1
- package/dist/service/useDataRoutes.js +59 -2
- package/dist/service/useDataRoutes.js.map +1 -1
- package/dist/service/useExtractRoutes.d.ts +4 -0
- package/dist/service/useExtractRoutes.d.ts.map +1 -0
- package/dist/service/useExtractRoutes.js +304 -0
- package/dist/service/useExtractRoutes.js.map +1 -0
- package/dist/service/usePageRoutes.d.ts +17 -0
- package/dist/service/usePageRoutes.d.ts.map +1 -1
- package/dist/service/usePageRoutes.js +1385 -483
- package/dist/service/usePageRoutes.js.map +1 -1
- package/dist/service/useSharedDataRoutes.d.ts.map +1 -1
- package/dist/service/useSharedDataRoutes.js +54 -2
- package/dist/service/useSharedDataRoutes.js.map +1 -1
- package/dist/settings.d.ts +27 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +40 -1
- package/dist/settings.js.map +1 -1
- package/dist/themes.d.ts +0 -5
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +3 -95
- package/dist/themes.js.map +1 -1
- package/migration-rules/v2-to-v3.md +277 -119
- package/package.json +5 -1
- package/{default-pages/application → required-pages/_shell}/page.html +56 -42
- package/required-pages/_shell/page.json +14 -0
- package/required-pages/_starters/page.html +534 -0
- package/required-pages/_starters/page.json +12 -0
- package/required-pages/builder/page.html +353 -43
- package/required-pages/builder/page.json +12 -10
- package/required-pages/pages/page.html +697 -924
- package/required-pages/pages/page.json +12 -10
- package/required-pages/settings/page.html +1879 -1753
- package/required-pages/settings/page.json +12 -10
- package/required-pages/synthos_apis/page.html +834 -845
- package/required-pages/synthos_apis/page.json +12 -10
- package/required-pages/synthos_scripts/page.html +74 -88
- package/required-pages/synthos_scripts/page.json +12 -10
- package/scripts/append-instructions.py +90 -0
- package/scripts/audit-instructions.py +76 -0
- package/scripts/cleanup-shell-markup.mjs +112 -0
- package/service-connectors/buffer/connector.json +46 -0
- package/service-connectors/canva/connector.json +67 -0
- package/service-connectors/elevenlabs/connector.json +1 -1
- package/src/builders/anthropic.ts +155 -30
- package/src/builders/claudecode.ts +310 -0
- package/src/builders/index.ts +7 -1
- package/src/builders/openai.ts +2 -1
- package/src/builders/types.ts +93 -32
- package/src/connectors/types.ts +8 -0
- package/src/init.ts +13 -7
- package/src/migrations.ts +187 -16
- package/src/models/anthropic.ts +140 -30
- package/src/models/chainOfThought.ts +33 -18
- package/src/models/index.ts +2 -2
- package/src/models/providers.ts +12 -3
- package/src/models/types.ts +21 -1
- package/src/pages.ts +271 -35
- package/src/service/createCompletePrompt.ts +6 -0
- package/src/service/mediaCache.ts +206 -0
- package/src/service/pageValidator.ts +337 -0
- package/src/service/server.ts +4 -0
- package/src/service/sharedTableSchema.ts +236 -0
- package/src/service/transformPage.ts +370 -260
- package/src/service/useApiRoutes.ts +282 -32
- package/src/service/useConnectorRoutes.ts +189 -34
- package/src/service/useDataRoutes.ts +198 -116
- package/src/service/useExtractRoutes.ts +331 -0
- package/src/service/usePageRoutes.ts +1411 -394
- package/src/service/useSharedDataRoutes.ts +184 -109
- package/src/settings.ts +65 -0
- package/src/themes.ts +78 -180
- package/starters/blank_starter/chat-history.json +1 -0
- package/starters/blank_starter/page.dark.png +0 -0
- package/starters/blank_starter/page.html +47 -0
- package/starters/blank_starter/page.json +13 -0
- package/starters/blank_starter/page.light.png +0 -0
- package/starters/calculator_starter/chat-history.json +1 -0
- package/starters/calculator_starter/page.dark.png +0 -0
- package/starters/calculator_starter/page.html +232 -0
- package/starters/calculator_starter/page.json +13 -0
- package/starters/calculator_starter/page.light.png +0 -0
- package/starters/calendar_starter/chat-history.json +1 -0
- package/starters/calendar_starter/page.dark.png +0 -0
- package/starters/calendar_starter/page.html +495 -0
- package/starters/calendar_starter/page.json +13 -0
- package/starters/calendar_starter/page.light.png +0 -0
- package/starters/chat_starter/chat-history.json +1 -0
- package/starters/chat_starter/page.dark.png +0 -0
- package/starters/chat_starter/page.html +351 -0
- package/starters/chat_starter/page.json +13 -0
- package/starters/chat_starter/page.light.png +0 -0
- package/starters/checklist_starter/chat-history.json +1 -0
- package/starters/checklist_starter/page.dark.png +0 -0
- package/starters/checklist_starter/page.html +437 -0
- package/starters/checklist_starter/page.json +13 -0
- package/starters/checklist_starter/page.light.png +0 -0
- package/starters/dashboard_starter/chat-history.json +1 -0
- package/starters/dashboard_starter/page.dark.png +0 -0
- package/starters/dashboard_starter/page.html +195 -0
- package/starters/dashboard_starter/page.json +13 -0
- package/starters/dashboard_starter/page.light.png +0 -0
- package/starters/form_starter/chat-history.json +1 -0
- package/starters/form_starter/page.dark.png +0 -0
- package/starters/form_starter/page.html +313 -0
- package/starters/form_starter/page.json +13 -0
- package/starters/form_starter/page.light.png +0 -0
- package/starters/gallery_starter/chat-history.json +1 -0
- package/starters/gallery_starter/page.dark.png +0 -0
- package/starters/gallery_starter/page.html +418 -0
- package/starters/gallery_starter/page.json +13 -0
- package/starters/gallery_starter/page.light.png +0 -0
- package/starters/generator_starter/chat-history.json +1 -0
- package/starters/generator_starter/page.dark.png +0 -0
- package/starters/generator_starter/page.html +261 -0
- package/starters/generator_starter/page.json +13 -0
- package/starters/generator_starter/page.light.png +0 -0
- package/starters/index.html +538 -0
- package/starters/kanban_starter/chat-history.json +1 -0
- package/starters/kanban_starter/page.dark.png +0 -0
- package/starters/kanban_starter/page.html +432 -0
- package/starters/kanban_starter/page.json +13 -0
- package/starters/kanban_starter/page.light.png +0 -0
- package/starters/presentation_builder/chat-history.json +1 -0
- package/starters/presentation_builder/page.dark.png +0 -0
- package/starters/presentation_builder/page.html +970 -0
- package/starters/presentation_builder/page.json +15 -0
- package/starters/presentation_builder/page.light.png +0 -0
- package/starters/presentation_builder/presentation_voice/voice_config.json +9 -0
- package/starters/pulse_starter/chat-history.json +1 -0
- package/starters/pulse_starter/page.dark.png +0 -0
- package/starters/pulse_starter/page.html +698 -0
- package/starters/pulse_starter/page.json +13 -0
- package/starters/pulse_starter/page.light.png +0 -0
- package/starters/quiz_starter/chat-history.json +1 -0
- package/starters/quiz_starter/page.dark.png +0 -0
- package/starters/quiz_starter/page.html +292 -0
- package/starters/quiz_starter/page.json +13 -0
- package/starters/quiz_starter/page.light.png +0 -0
- package/starters/reference_starter/chat-history.json +1 -0
- package/starters/reference_starter/page.dark.png +0 -0
- package/starters/reference_starter/page.html +250 -0
- package/starters/reference_starter/page.json +13 -0
- package/starters/reference_starter/page.light.png +0 -0
- package/starters/retro_game_starter/chat-history.json +1 -0
- package/starters/retro_game_starter/page.dark.png +0 -0
- package/{default-pages → starters}/retro_game_starter/page.html +1281 -1308
- package/starters/retro_game_starter/page.json +15 -0
- package/starters/retro_game_starter/page.light.png +0 -0
- package/starters/roster_starter/chat-history.json +1 -0
- package/starters/roster_starter/page.dark.png +0 -0
- package/starters/roster_starter/page.html +600 -0
- package/starters/roster_starter/page.json +13 -0
- package/starters/roster_starter/page.light.png +0 -0
- package/starters/server.js +182 -0
- package/starters/start.cmd +1 -0
- package/starters/timeline_starter/chat-history.json +1 -0
- package/starters/timeline_starter/page.dark.png +0 -0
- package/starters/timeline_starter/page.html +446 -0
- package/starters/timeline_starter/page.json +13 -0
- package/starters/timeline_starter/page.light.png +0 -0
- package/starters/tutorial_starter/chat-history.json +1 -0
- package/starters/tutorial_starter/page.dark.png +0 -0
- package/starters/tutorial_starter/page.html +283 -0
- package/starters/tutorial_starter/page.json +13 -0
- package/starters/tutorial_starter/page.light.png +0 -0
- package/static-files/agent.v3.js +122 -0
- package/static-files/connector.v3.js +48 -0
- package/static-files/extract.v3.js +188 -0
- package/static-files/helpers.v3.js +50 -6
- package/static-files/page-bridge.js +114 -0
- package/static-files/page.v3.js +1292 -1290
- package/static-files/script.v3.js +32 -0
- package/static-files/server.v3.js +89 -0
- package/static-files/shell-bridge.v3.js +174 -0
- package/static-files/shell-modals.v3.js +521 -0
- package/static-files/{shell.css → shell.v3.css} +271 -22
- package/static-files/shell.v3.js +1865 -0
- package/static-files/storage.v3.js +176 -0
- package/tests/anthropic.spec.ts +42 -7
- package/tests/builders.spec.ts +72 -4
- package/tests/pageValidator.spec.ts +548 -0
- package/tests/profiles.spec.ts +122 -0
- package/tests/providers.spec.ts +1 -1
- package/tests/sharedTableSchema.spec.ts +242 -0
- package/tests/transformPage.spec.ts +62 -81
- package/default-pages/application/page.json +0 -10
- package/default-pages/retro_game_starter/page.json +0 -12
- package/default-pages/sidebar_page/page.html +0 -51
- package/default-pages/sidebar_page/page.json +0 -10
- package/default-pages/two-panel_page/page.html +0 -68
- package/default-pages/two-panel_page/page.json +0 -10
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
+
import * as fs from "fs/promises";
|
|
2
3
|
import AdmZip from "adm-zip";
|
|
3
|
-
import { listPages, loadPageMetadata, PageMetadata, savePageMetadata, deletePage, copyPage, savePageState,
|
|
4
|
+
import { listPages, loadPageMetadata, PageMetadata, savePageMetadata, deletePage, copyPage, savePageState, clearChanges, PAGE_VERSION } from "../pages";
|
|
4
5
|
import { checkIfExists, findFileInFolders, listFiles, listFolders, loadFile } from "../files";
|
|
5
6
|
import {getModelEntry, loadSettings, saveSettings, ServicesConfig } from "../settings";
|
|
6
7
|
import { Application } from 'express';
|
|
@@ -8,7 +9,7 @@ import express from 'express';
|
|
|
8
9
|
import { SynthOSConfig } from "../init";
|
|
9
10
|
import { createCompletePrompt, PROVIDERS } from "./createCompletePrompt";
|
|
10
11
|
import { generateDefaultImage, generateImage } from "./generateImage";
|
|
11
|
-
import {
|
|
12
|
+
import { createMediaCache } from "./mediaCache";
|
|
12
13
|
import { requiresSettings } from "./requiresSettings";
|
|
13
14
|
import { executeScript } from "../scripts";
|
|
14
15
|
import { listThemes, loadTheme, loadThemeInfo, loadThemeVersion } from "../themes";
|
|
@@ -35,6 +36,25 @@ interface ServiceDefinition {
|
|
|
35
36
|
exclusive?: string;
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Greeting text applied to any page upgraded by `/api/pages/:name/upgrade`
|
|
41
|
+
* when the page does not already carry a user-authored greeting. Shown as the
|
|
42
|
+
* first assistant message when the migrated page loads.
|
|
43
|
+
*/
|
|
44
|
+
const MIGRATED_PAGE_GREETING =
|
|
45
|
+
"This page was just migrated to v3. If anything looks off or stopped working, let me know what you see and I'll help fix it.";
|
|
46
|
+
|
|
47
|
+
const BRAINSTORM_SCHEMA = {
|
|
48
|
+
type: 'object',
|
|
49
|
+
additionalProperties: false,
|
|
50
|
+
required: ['response', 'prompt', 'suggestions'],
|
|
51
|
+
properties: {
|
|
52
|
+
response: { type: 'string' },
|
|
53
|
+
prompt: { type: 'string' },
|
|
54
|
+
suggestions: { type: 'array', items: { type: 'string' } },
|
|
55
|
+
},
|
|
56
|
+
} as const;
|
|
57
|
+
|
|
38
58
|
const SERVICE_REGISTRY: ServiceDefinition[] = [
|
|
39
59
|
{
|
|
40
60
|
id: 'brave-search',
|
|
@@ -50,6 +70,10 @@ const SERVICE_REGISTRY: ServiceDefinition[] = [
|
|
|
50
70
|
|
|
51
71
|
export function useApiRoutes(config: SynthOSConfig, app: Application, customizer?: Customizer): void {
|
|
52
72
|
const sp = config.storageProvider;
|
|
73
|
+
const mediaCache = createMediaCache({
|
|
74
|
+
storage: sp,
|
|
75
|
+
cacheRoot: path.join(config.pagesFolder, 'cache'),
|
|
76
|
+
});
|
|
53
77
|
|
|
54
78
|
// List pages
|
|
55
79
|
app.get('/api/pages', async (req, res) => {
|
|
@@ -130,6 +154,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
130
154
|
const existingMeta = await loadPageMetadata(config, finalName);
|
|
131
155
|
const metadata: PageMetadata = {
|
|
132
156
|
title: existingMeta?.title ?? '',
|
|
157
|
+
description: existingMeta?.description ?? '',
|
|
133
158
|
categories: existingMeta?.categories ?? [],
|
|
134
159
|
pinned: existingMeta?.pinned ?? false,
|
|
135
160
|
showInAll: existingMeta?.showInAll ?? true,
|
|
@@ -137,6 +162,8 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
137
162
|
lastModified: now,
|
|
138
163
|
pageVersion: existingMeta?.pageVersion ?? PAGE_VERSION,
|
|
139
164
|
mode: existingMeta?.mode ?? 'unlocked',
|
|
165
|
+
greeting: existingMeta?.greeting ?? '',
|
|
166
|
+
firstRunGreeting: existingMeta?.firstRunGreeting ?? '',
|
|
140
167
|
};
|
|
141
168
|
await savePageMetadata(config, finalName, metadata);
|
|
142
169
|
|
|
@@ -157,6 +184,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
157
184
|
} else {
|
|
158
185
|
const defaults: PageMetadata = {
|
|
159
186
|
title: '',
|
|
187
|
+
description: '',
|
|
160
188
|
categories: [],
|
|
161
189
|
pinned: false,
|
|
162
190
|
showInAll: true,
|
|
@@ -164,6 +192,8 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
164
192
|
lastModified: '',
|
|
165
193
|
pageVersion: 0,
|
|
166
194
|
mode: 'unlocked',
|
|
195
|
+
greeting: '',
|
|
196
|
+
firstRunGreeting: '',
|
|
167
197
|
};
|
|
168
198
|
res.json(defaults);
|
|
169
199
|
}
|
|
@@ -184,6 +214,10 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
184
214
|
res.status(400).json({ error: 'title must be a string' });
|
|
185
215
|
return;
|
|
186
216
|
}
|
|
217
|
+
if ('description' in body && typeof body.description !== 'string') {
|
|
218
|
+
res.status(400).json({ error: 'description must be a string' });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
187
221
|
if ('categories' in body && !Array.isArray(body.categories)) {
|
|
188
222
|
res.status(400).json({ error: 'categories must be an array' });
|
|
189
223
|
return;
|
|
@@ -205,6 +239,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
205
239
|
const existing = await loadPageMetadata(config, name, config.requiredPagesFolders);
|
|
206
240
|
const metadata: PageMetadata = {
|
|
207
241
|
title: existing?.title ?? '',
|
|
242
|
+
description: existing?.description ?? '',
|
|
208
243
|
categories: existing?.categories ?? [],
|
|
209
244
|
pinned: existing?.pinned ?? false,
|
|
210
245
|
showInAll: existing?.showInAll ?? true,
|
|
@@ -212,10 +247,13 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
212
247
|
lastModified: existing?.lastModified ?? '',
|
|
213
248
|
pageVersion: existing?.pageVersion ?? 0,
|
|
214
249
|
mode: existing?.mode ?? 'unlocked',
|
|
250
|
+
greeting: existing?.greeting ?? '',
|
|
251
|
+
firstRunGreeting: existing?.firstRunGreeting ?? '',
|
|
215
252
|
};
|
|
216
253
|
|
|
217
254
|
// Overlay provided fields
|
|
218
255
|
if ('title' in body) metadata.title = body.title;
|
|
256
|
+
if ('description' in body) metadata.description = body.description;
|
|
219
257
|
if ('categories' in body) metadata.categories = body.categories;
|
|
220
258
|
if ('pinned' in body) metadata.pinned = body.pinned;
|
|
221
259
|
if ('showInAll' in body) metadata.showInAll = body.showInAll;
|
|
@@ -266,6 +304,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
266
304
|
if (!metadata) {
|
|
267
305
|
metadata = {
|
|
268
306
|
title: '',
|
|
307
|
+
description: '',
|
|
269
308
|
categories: [],
|
|
270
309
|
pinned: false,
|
|
271
310
|
showInAll: true,
|
|
@@ -273,6 +312,8 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
273
312
|
lastModified: '',
|
|
274
313
|
pageVersion: 0,
|
|
275
314
|
mode: 'unlocked',
|
|
315
|
+
greeting: '',
|
|
316
|
+
firstRunGreeting: '',
|
|
276
317
|
};
|
|
277
318
|
}
|
|
278
319
|
|
|
@@ -473,10 +514,46 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
473
514
|
const { prompt, shape, style } = req.body;
|
|
474
515
|
const builder = getModelEntry(settings, 'builder');
|
|
475
516
|
const { configuration, imageQuality, provider } = builder;
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
517
|
+
|
|
518
|
+
// Only cache OpenAI image generations
|
|
519
|
+
if (provider !== 'OpenAI') {
|
|
520
|
+
const response = await generateDefaultImage();
|
|
521
|
+
if (response.completed) {
|
|
522
|
+
res.json(response.value);
|
|
523
|
+
} else {
|
|
524
|
+
res.status(500).send(response.error?.message);
|
|
525
|
+
}
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const cacheEnabled = settings.cache?.enabled !== false;
|
|
530
|
+
const noCache = req.headers['x-no-cache'] === 'true' || req.query.nocache === '1';
|
|
531
|
+
const cacheKey = `image:v1:${prompt}:${shape}:${imageQuality}:${style}`;
|
|
532
|
+
|
|
533
|
+
// Check cache
|
|
534
|
+
if (cacheEnabled && !noCache) {
|
|
535
|
+
const cached = await mediaCache.get('images', cacheKey);
|
|
536
|
+
if (cached.hit) {
|
|
537
|
+
const base64 = cached.buffer.toString('base64');
|
|
538
|
+
res.json({ url: `data:${cached.contentType};base64,${base64}` });
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Generate fresh
|
|
544
|
+
const response = await generateImage({ apiKey: configuration.apiKey, prompt, shape, quality: imageQuality, style });
|
|
479
545
|
if (response.completed) {
|
|
546
|
+
// Cache the result
|
|
547
|
+
if (cacheEnabled) {
|
|
548
|
+
const dataUrl = response.value!.url;
|
|
549
|
+
const base64Data = dataUrl.split(',')[1];
|
|
550
|
+
if (base64Data) {
|
|
551
|
+
const buffer = Buffer.from(base64Data, 'base64');
|
|
552
|
+
await mediaCache.put('images', cacheKey, buffer, 'image/png', {
|
|
553
|
+
prompt, shape, quality: imageQuality, style,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
480
557
|
res.json(response.value);
|
|
481
558
|
} else {
|
|
482
559
|
res.status(500).send(response.error?.message);
|
|
@@ -484,18 +561,57 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
484
561
|
});
|
|
485
562
|
});
|
|
486
563
|
|
|
487
|
-
// Define a route to generate a completion
|
|
564
|
+
// Define a route to generate a completion. When `schema` is provided, the model
|
|
565
|
+
// is constrained to emit JSON conforming to it (Anthropic output_config /
|
|
566
|
+
// OpenAI json_schema) and the parsed object is returned directly. Otherwise a
|
|
567
|
+
// plain text completion is returned as { answer: string }.
|
|
488
568
|
app.post('/api/generate/completion', async (req, res) => {
|
|
489
|
-
await requiresSettings(res, config, async (
|
|
490
|
-
const { prompt, temperature } = req.body;
|
|
569
|
+
await requiresSettings(res, config, async () => {
|
|
570
|
+
const { prompt, temperature, schema } = req.body;
|
|
571
|
+
if (typeof prompt !== 'string' || !prompt.trim()) {
|
|
572
|
+
res.status(400).json({ error: 'prompt is required' });
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (schema !== undefined && (typeof schema !== 'object' || schema === null || Array.isArray(schema))) {
|
|
576
|
+
res.status(400).json({ error: 'schema must be a JSON schema object' });
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
491
579
|
const completePrompt = await createCompletePrompt(config, 'chat', req.body.model);
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
580
|
+
const userMessage: { role: 'user'; content: string } = { role: 'user', content: prompt };
|
|
581
|
+
const result = await completePrompt({
|
|
582
|
+
prompt: userMessage,
|
|
583
|
+
temperature,
|
|
584
|
+
...(schema ? { outputSchema: schema, jsonSchema: schema } : {}),
|
|
585
|
+
});
|
|
586
|
+
if (!result.completed) {
|
|
587
|
+
console.error(result.error);
|
|
588
|
+
res.status(500).send(result.error?.message);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const text = typeof result.value === 'string' ? result.value : '';
|
|
592
|
+
if (schema) {
|
|
593
|
+
let parsed: unknown;
|
|
594
|
+
if (typeof result.value === 'object' && result.value !== null) {
|
|
595
|
+
parsed = result.value;
|
|
596
|
+
} else {
|
|
597
|
+
let candidate = text.replace(/^```(?:json)?\s*/i, '').replace(/\s*```\s*$/, '').trim();
|
|
598
|
+
const start = candidate.indexOf('{');
|
|
599
|
+
const end = candidate.lastIndexOf('}');
|
|
600
|
+
if (start !== -1 && end > start) {
|
|
601
|
+
candidate = candidate.substring(start, end + 1);
|
|
602
|
+
}
|
|
603
|
+
try {
|
|
604
|
+
parsed = JSON.parse(candidate);
|
|
605
|
+
} catch {
|
|
606
|
+
console.error('completion: schema-mode response was not parseable JSON:', text);
|
|
607
|
+
res.status(502).json({ error: 'Model did not return JSON conforming to schema', raw: text });
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
res.json(parsed);
|
|
612
|
+
return;
|
|
498
613
|
}
|
|
614
|
+
res.json({ answer: text });
|
|
499
615
|
});
|
|
500
616
|
});
|
|
501
617
|
|
|
@@ -526,16 +642,10 @@ If you see a conversation between ${productName} and the User. Asses what they'r
|
|
|
526
642
|
|
|
527
643
|
${productName} exposes table storage and chat completion api's that every page can use. If the user wants to store something or use AI, your prompt should suggest using table storage or make llm calls.
|
|
528
644
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
"suggestions": ["Short clickable option A", "Short clickable option B", "Short clickable option C"]
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
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.
|
|
537
|
-
|
|
538
|
-
Return ONLY the JSON object.`};
|
|
645
|
+
Field guidance:
|
|
646
|
+
- response — Your conversational reply: explanations, options, suggestions. Markdown OK.
|
|
647
|
+
- prompt — A clean, actionable instruction ready to paste into ${productName} chat to build what was discussed. Update this each exchange to reflect the latest brainstorm state. Empty string is fine before the user has a clear direction.
|
|
648
|
+
- suggestions — 2-4 short phrases (under 60 characters each) the user can click to continue the conversation. Always provide suggestions.`};
|
|
539
649
|
|
|
540
650
|
// Format multi-turn conversation into a single prompt
|
|
541
651
|
const formatted = (messages as { role: string; content: string }[]).map(m =>
|
|
@@ -544,15 +654,35 @@ Return ONLY the JSON object.`};
|
|
|
544
654
|
|
|
545
655
|
const prompt: { role: 'user'; content: string } = { role: 'user', content: formatted };
|
|
546
656
|
|
|
547
|
-
const result = await completePrompt({
|
|
657
|
+
const result = await completePrompt({
|
|
658
|
+
prompt,
|
|
659
|
+
system,
|
|
660
|
+
outputSchema: BRAINSTORM_SCHEMA,
|
|
661
|
+
jsonSchema: BRAINSTORM_SCHEMA,
|
|
662
|
+
});
|
|
548
663
|
if (result.completed) {
|
|
549
|
-
let response =
|
|
664
|
+
let response = '';
|
|
550
665
|
let brainstormPrompt = '';
|
|
551
666
|
let suggestions: string[] = [];
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
667
|
+
let parsed: Record<string, unknown> | null = null;
|
|
668
|
+
if (typeof result.value === 'object' && result.value !== null) {
|
|
669
|
+
parsed = result.value as Record<string, unknown>;
|
|
670
|
+
} else if (typeof result.value === 'string') {
|
|
671
|
+
let candidate = result.value
|
|
672
|
+
.replace(/^```(?:json)?\s*/i, '')
|
|
673
|
+
.replace(/\s*```\s*$/, '')
|
|
674
|
+
.trim();
|
|
675
|
+
const start = candidate.indexOf('{');
|
|
676
|
+
const end = candidate.lastIndexOf('}');
|
|
677
|
+
if (start !== -1 && end > start) {
|
|
678
|
+
candidate = candidate.substring(start, end + 1);
|
|
679
|
+
}
|
|
680
|
+
try {
|
|
681
|
+
parsed = JSON.parse(candidate) as Record<string, unknown>;
|
|
682
|
+
} catch {
|
|
683
|
+
console.error('brainstorm: response was not parseable JSON:', result.value);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
556
686
|
if (parsed) {
|
|
557
687
|
if (typeof parsed.response === 'string') response = parsed.response;
|
|
558
688
|
if (typeof parsed.prompt === 'string') brainstormPrompt = parsed.prompt;
|
|
@@ -883,12 +1013,20 @@ Return ONLY the JSON object.`};
|
|
|
883
1013
|
await sp.copyFolderRecursive(folderPath, path.join(migratedFolder, name));
|
|
884
1014
|
}
|
|
885
1015
|
|
|
886
|
-
// Clear stale
|
|
887
|
-
await
|
|
1016
|
+
// Clear stale change files (undo snapshots from the old page version)
|
|
1017
|
+
await clearChanges(config, name);
|
|
888
1018
|
|
|
889
1019
|
// Update metadata
|
|
890
1020
|
metadata.pageVersion = PAGE_VERSION;
|
|
891
1021
|
metadata.lastModified = new Date().toISOString();
|
|
1022
|
+
|
|
1023
|
+
// Assign a generic greeting so the shell shows a helpful first
|
|
1024
|
+
// message explaining this page was auto-migrated. Only set if the
|
|
1025
|
+
// page does not already have a user-authored greeting.
|
|
1026
|
+
if (!metadata.greeting || metadata.greeting.trim().length === 0) {
|
|
1027
|
+
metadata.greeting = MIGRATED_PAGE_GREETING;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
892
1030
|
await savePageMetadata(config, name, metadata);
|
|
893
1031
|
|
|
894
1032
|
res.json({ upgraded: true, fromVersion: currentVersion, toVersion: PAGE_VERSION });
|
|
@@ -987,4 +1125,116 @@ Return ONLY the JSON object.`};
|
|
|
987
1125
|
}
|
|
988
1126
|
});
|
|
989
1127
|
});
|
|
1128
|
+
|
|
1129
|
+
// -----------------------------------------------------------------------
|
|
1130
|
+
// Cache management routes
|
|
1131
|
+
// -----------------------------------------------------------------------
|
|
1132
|
+
|
|
1133
|
+
app.get('/api/cache/stats', async (_req, res) => {
|
|
1134
|
+
try {
|
|
1135
|
+
const cacheStats = await mediaCache.stats();
|
|
1136
|
+
res.json(cacheStats);
|
|
1137
|
+
} catch (err: unknown) {
|
|
1138
|
+
res.status(500).json({ error: (err as Error).message });
|
|
1139
|
+
}
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
app.delete('/api/cache/images', async (_req, res) => {
|
|
1143
|
+
try {
|
|
1144
|
+
await mediaCache.clearCategory('images');
|
|
1145
|
+
res.json({ cleared: 'images' });
|
|
1146
|
+
} catch (err: unknown) {
|
|
1147
|
+
res.status(500).json({ error: (err as Error).message });
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
app.delete('/api/cache/audio', async (_req, res) => {
|
|
1152
|
+
try {
|
|
1153
|
+
await mediaCache.clearCategory('audio');
|
|
1154
|
+
res.json({ cleared: 'audio' });
|
|
1155
|
+
} catch (err: unknown) {
|
|
1156
|
+
res.status(500).json({ error: (err as Error).message });
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
app.delete('/api/cache', async (_req, res) => {
|
|
1161
|
+
try {
|
|
1162
|
+
await mediaCache.clearAll();
|
|
1163
|
+
res.json({ cleared: 'all' });
|
|
1164
|
+
} catch (err: unknown) {
|
|
1165
|
+
res.status(500).json({ error: (err as Error).message });
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
// Serve a saved starter screenshot (page.light.png / page.dark.png) from
|
|
1170
|
+
// the top-level starters/<name>/ folder so the carousel can display them.
|
|
1171
|
+
app.get('/api/starters/:name/screenshot/:variant', async (req, res) => {
|
|
1172
|
+
try {
|
|
1173
|
+
const { name, variant } = req.params;
|
|
1174
|
+
if (variant !== 'light' && variant !== 'dark') {
|
|
1175
|
+
res.status(400).json({ error: 'variant must be "light" or "dark"' });
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
1179
|
+
res.status(400).json({ error: 'invalid starter name' });
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
const target = path.join(process.cwd(), 'starters', name, `page.${variant}.png`);
|
|
1183
|
+
try {
|
|
1184
|
+
await fs.access(target);
|
|
1185
|
+
} catch {
|
|
1186
|
+
res.status(404).json({ error: 'screenshot not found' });
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
res.sendFile(target, { headers: { 'Cache-Control': 'public, max-age=300' } });
|
|
1190
|
+
} catch (err: unknown) {
|
|
1191
|
+
console.error(err);
|
|
1192
|
+
res.status(500).json({ error: (err as Error).message });
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
// Save a captured screenshot for a starter page into the top-level
|
|
1197
|
+
// starters/<name>/ folder so the image lives alongside the snapshot.
|
|
1198
|
+
app.post(
|
|
1199
|
+
'/api/starters/:name/screenshot/:variant',
|
|
1200
|
+
express.raw({ type: 'image/png', limit: '20mb' }),
|
|
1201
|
+
async (req, res) => {
|
|
1202
|
+
try {
|
|
1203
|
+
const { name, variant } = req.params;
|
|
1204
|
+
|
|
1205
|
+
if (variant !== 'light' && variant !== 'dark') {
|
|
1206
|
+
res.status(400).json({ error: 'variant must be "light" or "dark"' });
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
1211
|
+
res.status(400).json({ error: 'invalid starter name' });
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
const png = req.body as Buffer;
|
|
1216
|
+
if (!png || png.length === 0) {
|
|
1217
|
+
res.status(400).json({ error: 'empty image body' });
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
// PNG signature: 89 50 4E 47 0D 0A 1A 0A
|
|
1221
|
+
if (png.length < 8 ||
|
|
1222
|
+
png[0] !== 0x89 || png[1] !== 0x50 || png[2] !== 0x4E || png[3] !== 0x47) {
|
|
1223
|
+
res.status(400).json({ error: 'body is not a PNG' });
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
const targetFolder = path.join(process.cwd(), 'starters', name);
|
|
1228
|
+
await fs.mkdir(targetFolder, { recursive: true });
|
|
1229
|
+
|
|
1230
|
+
const target = path.join(targetFolder, `page.${variant}.png`);
|
|
1231
|
+
await fs.writeFile(target, png);
|
|
1232
|
+
|
|
1233
|
+
res.json({ ok: true, paths: [target], bytes: png.length });
|
|
1234
|
+
} catch (err: unknown) {
|
|
1235
|
+
console.error(err);
|
|
1236
|
+
res.status(500).json({ error: (err as Error).message });
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
);
|
|
990
1240
|
}
|