synthos 0.8.0 → 0.10.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 +1 -1
- package/default-pages/application/page.html +42 -0
- package/default-pages/application/page.json +10 -0
- package/default-pages/elevenlabs_effects_studio/page.html +1363 -0
- package/default-pages/elevenlabs_effects_studio/page.json +11 -0
- package/default-pages/elevenlabs_voice_studio/page.html +801 -0
- package/default-pages/elevenlabs_voice_studio/page.json +11 -0
- package/default-pages/{json_tools.html → json_tools/page.html} +13 -11
- package/default-pages/json_tools/page.json +10 -0
- package/default-pages/my_notes/notes/a1b2c3d4-e5f6-7890-abcd-ef1234567890.json +5 -0
- package/default-pages/my_notes/page.html +132 -0
- package/default-pages/{my_notes.json → my_notes/page.json} +2 -2
- package/default-pages/neon_asteroids/files/Ambient_Space.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Ambient_Space2.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Ambient_Space3.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Asteroid_Explosion.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Hyperspace_Jump.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Laser_Fire.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Menu_Navigate.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Power_Up_Collect.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Saucer_Alert.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Ship_Thrust.mp3 +0 -0
- package/default-pages/neon_asteroids/files/effects.json +74 -0
- package/default-pages/neon_asteroids/page.html +1803 -0
- package/default-pages/{neon_asteroids.json → neon_asteroids/page.json} +3 -3
- package/default-pages/{oregon_trail.html → oregon_trail/page.html} +16 -30
- package/default-pages/{oregon_trail.json → oregon_trail/page.json} +2 -2
- package/default-pages/retro_game_starter/page.html +1308 -0
- package/default-pages/retro_game_starter/page.json +12 -0
- package/default-pages/{sidebar_page.html → sidebar_page/page.html} +12 -10
- package/default-pages/sidebar_page/page.json +10 -0
- package/default-pages/{solar_explorer.html → solar_explorer/page.html} +15 -12
- package/default-pages/{solar_explorer.json → solar_explorer/page.json} +2 -2
- package/default-pages/{solar_tutorial.html → solar_tutorial/page.html} +12 -10
- package/default-pages/solar_tutorial/page.json +10 -0
- package/default-pages/{two-panel_page.html → two-panel_page/page.html} +13 -11
- package/default-pages/two-panel_page/page.json +10 -0
- package/default-pages/{us_map.html → us_map/page.html} +193 -192
- package/default-pages/{us_map.json → us_map/page.json} +12 -12
- package/default-pages/{us_map_1850.html → us_map_1850/page.html} +326 -325
- package/default-pages/{us_map_1850.json → us_map_1850/page.json} +12 -12
- package/default-pages/{western_cities_1850.html → western_cities_1850/page.html} +527 -526
- package/default-pages/{western_cities_1850.json → western_cities_1850/page.json} +12 -12
- package/default-themes/aurora-dawn.json +19 -0
- package/default-themes/aurora-dawn.v3.css +198 -0
- package/default-themes/aurora-dusk.json +19 -0
- package/default-themes/aurora-dusk.v3.css +200 -0
- package/default-themes/cosmos-dawn.json +19 -0
- package/default-themes/cosmos-dawn.v3.css +198 -0
- package/default-themes/cosmos-dusk.json +19 -0
- package/default-themes/cosmos-dusk.v3.css +200 -0
- package/default-themes/high-contrast-dark.json +19 -0
- package/default-themes/high-contrast-dark.v3.css +200 -0
- package/default-themes/high-contrast-light.json +19 -0
- package/default-themes/high-contrast-light.v3.css +198 -0
- package/default-themes/nebula-dawn.v2.css +110 -0
- package/default-themes/nebula-dawn.v3.css +199 -0
- package/default-themes/nebula-dusk.v2.css +104 -0
- package/default-themes/nebula-dusk.v3.css +201 -0
- package/default-themes/solar-flare-dawn.json +19 -0
- package/default-themes/solar-flare-dawn.v3.css +198 -0
- package/default-themes/solar-flare-dusk.json +19 -0
- package/default-themes/solar-flare-dusk.v3.css +200 -0
- package/dist/agents/index.d.ts +1 -1
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +2 -1
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/openclaw/gatewayManager.d.ts +4 -0
- package/dist/agents/openclaw/gatewayManager.d.ts.map +1 -1
- package/dist/agents/openclaw/gatewayManager.js +27 -11
- package/dist/agents/openclaw/gatewayManager.js.map +1 -1
- package/dist/agents/openclaw/openclawProvider.d.ts.map +1 -1
- package/dist/agents/openclaw/openclawProvider.js +2 -4
- package/dist/agents/openclaw/openclawProvider.js.map +1 -1
- package/dist/agents/openclaw/sshTunnelManager.d.ts +2 -0
- package/dist/agents/openclaw/sshTunnelManager.d.ts.map +1 -1
- package/dist/agents/openclaw/sshTunnelManager.js +31 -12
- package/dist/agents/openclaw/sshTunnelManager.js.map +1 -1
- package/dist/builders/anthropic.d.ts +31 -0
- package/dist/builders/anthropic.d.ts.map +1 -0
- package/dist/builders/anthropic.js +227 -0
- package/dist/builders/anthropic.js.map +1 -0
- package/dist/builders/fireworksai.d.ts +9 -0
- package/dist/builders/fireworksai.d.ts.map +1 -0
- package/dist/builders/fireworksai.js +57 -0
- package/dist/builders/fireworksai.js.map +1 -0
- package/dist/builders/index.d.ts +13 -0
- package/dist/builders/index.d.ts.map +1 -0
- package/dist/builders/index.js +31 -0
- package/dist/builders/index.js.map +1 -0
- package/dist/builders/openai.d.ts +8 -0
- package/dist/builders/openai.d.ts.map +1 -0
- package/dist/builders/openai.js +87 -0
- package/dist/builders/openai.js.map +1 -0
- package/dist/builders/types.d.ts +54 -0
- package/dist/builders/types.d.ts.map +1 -0
- package/dist/builders/types.js +211 -0
- package/dist/builders/types.js.map +1 -0
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/index.js +3 -2
- package/dist/connectors/index.js.map +1 -1
- package/dist/connectors/registry.d.ts +2 -1
- package/dist/connectors/registry.d.ts.map +1 -1
- package/dist/connectors/registry.js +31 -8
- package/dist/connectors/registry.js.map +1 -1
- package/dist/customizer/Customizer.d.ts +62 -0
- package/dist/customizer/Customizer.d.ts.map +1 -0
- package/dist/customizer/Customizer.js +134 -0
- package/dist/customizer/Customizer.js.map +1 -0
- package/dist/customizer/index.d.ts +4 -0
- package/dist/customizer/index.d.ts.map +1 -0
- package/dist/customizer/index.js +9 -0
- package/dist/customizer/index.js.map +1 -0
- package/dist/files.d.ts +16 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +60 -1
- package/dist/files.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +12 -6
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +150 -133
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +23 -10
- package/dist/migrations.js.map +1 -1
- package/dist/models/anthropic.d.ts +4 -2
- package/dist/models/anthropic.d.ts.map +1 -1
- package/dist/models/anthropic.js +33 -6
- package/dist/models/anthropic.js.map +1 -1
- package/dist/models/fireworksai.d.ts.map +1 -1
- package/dist/models/fireworksai.js +9 -1
- package/dist/models/fireworksai.js.map +1 -1
- package/dist/models/index.d.ts +1 -1
- 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/openai.d.ts +1 -1
- package/dist/models/openai.d.ts.map +1 -1
- package/dist/models/openai.js +24 -3
- package/dist/models/openai.js.map +1 -1
- package/dist/models/types.d.ts +20 -1
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js +6 -1
- package/dist/models/types.js.map +1 -1
- package/dist/pages.d.ts +34 -10
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +229 -79
- package/dist/pages.js.map +1 -1
- package/dist/service/createCompletePrompt.d.ts +2 -1
- package/dist/service/createCompletePrompt.d.ts.map +1 -1
- package/dist/service/createCompletePrompt.js +2 -2
- package/dist/service/createCompletePrompt.js.map +1 -1
- package/dist/service/requiresSettings.d.ts +2 -1
- package/dist/service/requiresSettings.d.ts.map +1 -1
- package/dist/service/requiresSettings.js +3 -3
- package/dist/service/requiresSettings.js.map +1 -1
- package/dist/service/server.d.ts +2 -1
- package/dist/service/server.d.ts.map +1 -1
- package/dist/service/server.js +37 -8
- package/dist/service/server.js.map +1 -1
- package/dist/service/transformPage.d.ts +47 -20
- package/dist/service/transformPage.d.ts.map +1 -1
- package/dist/service/transformPage.js +514 -293
- package/dist/service/transformPage.js.map +1 -1
- package/dist/service/useAgentRoutes.d.ts +2 -1
- package/dist/service/useAgentRoutes.d.ts.map +1 -1
- package/dist/service/useAgentRoutes.js +17 -14
- package/dist/service/useAgentRoutes.js.map +1 -1
- package/dist/service/useApiRoutes.d.ts +2 -1
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +287 -172
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.js +17 -17
- package/dist/service/useConnectorRoutes.js.map +1 -1
- package/dist/service/useDataRoutes.d.ts.map +1 -1
- package/dist/service/useDataRoutes.js +13 -10
- package/dist/service/useDataRoutes.js.map +1 -1
- package/dist/service/useFileRoutes.d.ts +4 -0
- package/dist/service/useFileRoutes.d.ts.map +1 -0
- package/dist/service/useFileRoutes.js +122 -0
- package/dist/service/useFileRoutes.js.map +1 -0
- package/dist/service/usePageRoutes.d.ts +2 -1
- package/dist/service/usePageRoutes.d.ts.map +1 -1
- package/dist/service/usePageRoutes.js +671 -74
- package/dist/service/usePageRoutes.js.map +1 -1
- package/dist/service/useSharedDataRoutes.d.ts +4 -0
- package/dist/service/useSharedDataRoutes.d.ts.map +1 -0
- package/dist/service/useSharedDataRoutes.js +107 -0
- package/dist/service/useSharedDataRoutes.js.map +1 -0
- package/dist/service/useSharedFileRoutes.d.ts +4 -0
- package/dist/service/useSharedFileRoutes.d.ts.map +1 -0
- package/dist/service/useSharedFileRoutes.js +121 -0
- package/dist/service/useSharedFileRoutes.js.map +1 -0
- package/dist/settings.d.ts +5 -3
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +12 -10
- package/dist/settings.js.map +1 -1
- package/dist/storage/FsStorageProvider.d.ts +25 -0
- package/dist/storage/FsStorageProvider.d.ts.map +1 -0
- package/dist/storage/FsStorageProvider.js +103 -0
- package/dist/storage/FsStorageProvider.js.map +1 -0
- package/dist/storage/StorageProvider.d.ts +31 -0
- package/dist/storage/StorageProvider.d.ts.map +1 -0
- package/dist/storage/StorageProvider.js +3 -0
- package/dist/storage/StorageProvider.js.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/synthos-cli.d.ts.map +1 -1
- package/dist/synthos-cli.js +4 -3
- package/dist/synthos-cli.js.map +1 -1
- package/dist/themes.d.ts +1 -0
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +65 -28
- package/dist/themes.js.map +1 -1
- package/migration-rules/v1-to-v2.md +193 -0
- package/migration-rules/v2-to-v3.md +481 -0
- package/package.json +11 -10
- package/required-pages/builder/page.html +43 -0
- package/required-pages/builder/page.json +10 -0
- package/required-pages/{pages.html → pages/page.html} +238 -233
- package/required-pages/pages/page.json +10 -0
- package/required-pages/{settings.html → settings/page.html} +389 -275
- package/required-pages/settings/page.json +10 -0
- package/required-pages/synthos_apis/page.html +846 -0
- package/required-pages/synthos_apis/page.json +10 -0
- package/required-pages/{synthos_scripts.html → synthos_scripts/page.html} +13 -11
- package/required-pages/synthos_scripts/page.json +10 -0
- package/src/agents/index.ts +1 -1
- package/src/agents/openclaw/gatewayManager.ts +22 -11
- package/src/agents/openclaw/openclawProvider.ts +2 -4
- package/src/agents/openclaw/sshTunnelManager.ts +19 -11
- package/src/builders/anthropic.ts +283 -0
- package/src/builders/fireworksai.ts +59 -0
- package/src/builders/index.ts +33 -0
- package/src/builders/openai.ts +89 -0
- package/src/builders/types.ts +261 -0
- package/src/connectors/index.ts +1 -1
- package/src/connectors/registry.ts +28 -8
- package/src/customizer/Customizer.ts +163 -0
- package/src/customizer/index.ts +5 -0
- package/src/files.ts +57 -0
- package/src/index.ts +3 -1
- package/src/init.ts +195 -145
- package/src/migrations.ts +30 -10
- package/src/models/anthropic.ts +40 -10
- package/src/models/fireworksai.ts +9 -2
- package/src/models/index.ts +1 -1
- package/src/models/openai.ts +26 -6
- package/src/models/types.ts +31 -1
- package/src/pages.ts +230 -77
- package/src/service/createCompletePrompt.ts +3 -2
- package/src/service/requiresSettings.ts +4 -3
- package/src/service/server.ts +36 -9
- package/src/service/transformPage.ts +557 -326
- package/src/service/useAgentRoutes.ts +19 -14
- package/src/service/useApiRoutes.ts +208 -84
- package/src/service/useConnectorRoutes.ts +18 -18
- package/src/service/useDataRoutes.ts +13 -10
- package/src/service/useFileRoutes.ts +128 -0
- package/src/service/usePageRoutes.ts +730 -81
- package/src/service/useSharedDataRoutes.ts +109 -0
- package/src/service/useSharedFileRoutes.ts +127 -0
- package/src/settings.ts +14 -10
- package/src/storage/FsStorageProvider.ts +87 -0
- package/src/storage/StorageProvider.ts +34 -0
- package/src/storage/index.ts +2 -0
- package/src/synthos-cli.ts +4 -3
- package/src/themes.ts +64 -27
- package/static-files/favicon.svg +12 -0
- package/static-files/fluentlm-instructions.llmd +868 -0
- package/static-files/fluentlm-instructions.md +1595 -0
- package/static-files/fluentlm.css +4844 -0
- package/static-files/fluentlm.js +3602 -0
- package/static-files/fluentlm.min.css +1 -0
- package/static-files/fluentlm.min.js +1 -0
- package/{page-scripts/helpers-v2.js → static-files/helpers.v3.js} +82 -0
- package/static-files/page.v3.js +1290 -0
- package/static-files/recommended-frameworks.llmd +81 -0
- package/static-files/recommended-frameworks.md +137 -0
- package/static-files/retro-game.js +877 -0
- package/static-files/shell.css +797 -0
- package/static-files/theme-dark.css +169 -0
- package/static-files/theme-light.css +169 -0
- package/tests/builders.spec.ts +139 -0
- package/tests/pages.spec.ts +54 -84
- package/tests/transformPage.spec.ts +299 -360
- package/default-pages/application.html +0 -40
- package/default-pages/application.json +0 -1
- package/default-pages/json_tools.json +0 -1
- package/default-pages/my_notes.html +0 -33
- package/default-pages/neon_asteroids.html +0 -77
- package/default-pages/sidebar_page.json +0 -1
- package/default-pages/solar_tutorial.json +0 -1
- package/default-pages/two-panel_page.json +0 -1
- package/dist/service/useGatewayRoutes.d.ts +0 -4
- package/dist/service/useGatewayRoutes.d.ts.map +0 -1
- package/dist/service/useGatewayRoutes.js +0 -168
- package/dist/service/useGatewayRoutes.js.map +0 -1
- package/page-scripts/page-v2.js +0 -656
- package/required-pages/builder.html +0 -48
- package/required-pages/builder.json +0 -1
- package/required-pages/pages.json +0 -1
- package/required-pages/settings.json +0 -1
- package/required-pages/synthos_apis.html +0 -327
- package/required-pages/synthos_apis.json +0 -1
- package/required-pages/synthos_scripts.json +0 -1
- package/src/connectors/airtable/connector.json +0 -27
- package/src/connectors/alpha-vantage/connector.json +0 -26
- package/src/connectors/brave-search/connector.json +0 -26
- package/src/connectors/cloudinary/connector.json +0 -27
- package/src/connectors/deepl/connector.json +0 -28
- package/src/connectors/elevenlabs/connector.json +0 -30
- package/src/connectors/giphy/connector.json +0 -27
- package/src/connectors/github/connector.json +0 -29
- package/src/connectors/huggingface/connector.json +0 -27
- package/src/connectors/imgur/connector.json +0 -29
- package/src/connectors/instagram/connector.json +0 -43
- package/src/connectors/jira/connector.json +0 -28
- package/src/connectors/mapbox/connector.json +0 -26
- package/src/connectors/nasa/connector.json +0 -27
- package/src/connectors/newsapi/connector.json +0 -27
- package/src/connectors/notion/connector.json +0 -28
- package/src/connectors/open-exchange-rates/connector.json +0 -27
- package/src/connectors/openweathermap/connector.json +0 -26
- package/src/connectors/pexels/connector.json +0 -27
- package/src/connectors/resend/connector.json +0 -29
- package/src/connectors/rss2json/connector.json +0 -27
- package/src/connectors/sendgrid/connector.json +0 -27
- package/src/connectors/spoonacular/connector.json +0 -28
- package/src/connectors/stability-ai/connector.json +0 -27
- package/src/connectors/twilio/connector.json +0 -28
- package/src/connectors/unsplash/connector.json +0 -27
- package/src/connectors/wolfram-alpha/connector.json +0 -26
- package/src/connectors/youtube-data/connector.json +0 -30
- /package/{dist/connectors → service-connectors}/airtable/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/alpha-vantage/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/brave-search/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/cloudinary/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/deepl/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/elevenlabs/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/giphy/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/github/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/huggingface/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/imgur/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/instagram/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/jira/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/mapbox/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/nasa/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/newsapi/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/notion/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/open-exchange-rates/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/openweathermap/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/pexels/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/resend/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/rss2json/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/sendgrid/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/spoonacular/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/stability-ai/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/twilio/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/unsplash/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/wolfram-alpha/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/youtube-data/connector.json +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import fs from "fs/promises";
|
|
3
2
|
import AdmZip from "adm-zip";
|
|
4
|
-
import { listPages, loadPageMetadata, PageMetadata, savePageMetadata,
|
|
5
|
-
import { checkIfExists,
|
|
3
|
+
import { listPages, loadPageMetadata, PageMetadata, savePageMetadata, deletePage, copyPage, savePageState, clearVersions, PAGE_VERSION } from "../pages";
|
|
4
|
+
import { checkIfExists, findFileInFolders, listFiles, listFolders, loadFile } from "../files";
|
|
6
5
|
import {getModelEntry, loadSettings, saveSettings, ServicesConfig } from "../settings";
|
|
7
6
|
import { Application } from 'express';
|
|
8
7
|
import express from 'express';
|
|
@@ -12,9 +11,10 @@ import { generateDefaultImage, generateImage } from "./generateImage";
|
|
|
12
11
|
import { chainOfThought } from "../models";
|
|
13
12
|
import { requiresSettings } from "./requiresSettings";
|
|
14
13
|
import { executeScript } from "../scripts";
|
|
15
|
-
import { listThemes, loadTheme, loadThemeInfo } from "../themes";
|
|
14
|
+
import { listThemes, loadTheme, loadThemeInfo, loadThemeVersion } from "../themes";
|
|
16
15
|
import { migratePage } from "../migrations";
|
|
17
16
|
import { loadPageWithFallback } from "./usePageRoutes";
|
|
17
|
+
import { Customizer } from "../customizer";
|
|
18
18
|
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
20
|
// Service registry
|
|
@@ -48,10 +48,12 @@ const SERVICE_REGISTRY: ServiceDefinition[] = [
|
|
|
48
48
|
}
|
|
49
49
|
];
|
|
50
50
|
|
|
51
|
-
export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
51
|
+
export function useApiRoutes(config: SynthOSConfig, app: Application, customizer?: Customizer): void {
|
|
52
|
+
const sp = config.storageProvider;
|
|
53
|
+
|
|
52
54
|
// List pages
|
|
53
55
|
app.get('/api/pages', async (req, res) => {
|
|
54
|
-
const pages = await listPages(config
|
|
56
|
+
const pages = await listPages(config, config.requiredPagesFolders);
|
|
55
57
|
res.json(pages);
|
|
56
58
|
});
|
|
57
59
|
|
|
@@ -97,13 +99,13 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
97
99
|
// Auto-append _1, _2, etc. on name conflicts
|
|
98
100
|
let finalName = pageName;
|
|
99
101
|
let suffix = 0;
|
|
100
|
-
while (await checkIfExists(path.join(config.pagesFolder, 'pages', finalName))) {
|
|
102
|
+
while (await sp.checkIfExists(path.join(config.pagesFolder, 'pages', finalName))) {
|
|
101
103
|
suffix++;
|
|
102
104
|
finalName = `${pageName}_${suffix}`;
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
const targetDir = path.join(config.pagesFolder, 'pages', finalName);
|
|
106
|
-
await ensureFolderExists(targetDir);
|
|
108
|
+
await sp.ensureFolderExists(targetDir);
|
|
107
109
|
|
|
108
110
|
// Extract entries with path traversal protection
|
|
109
111
|
for (const entry of entries) {
|
|
@@ -119,13 +121,13 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
119
121
|
continue;
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
await ensureFolderExists(path.dirname(resolvedPath));
|
|
123
|
-
await
|
|
124
|
+
await sp.ensureFolderExists(path.dirname(resolvedPath));
|
|
125
|
+
await sp.saveBuffer(resolvedPath, entry.getData());
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
// Update metadata: set createdDate and lastModified to now
|
|
127
129
|
const now = new Date().toISOString();
|
|
128
|
-
const existingMeta = await loadPageMetadata(config
|
|
130
|
+
const existingMeta = await loadPageMetadata(config, finalName);
|
|
129
131
|
const metadata: PageMetadata = {
|
|
130
132
|
title: existingMeta?.title ?? '',
|
|
131
133
|
categories: existingMeta?.categories ?? [],
|
|
@@ -136,7 +138,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
136
138
|
pageVersion: existingMeta?.pageVersion ?? PAGE_VERSION,
|
|
137
139
|
mode: existingMeta?.mode ?? 'unlocked',
|
|
138
140
|
};
|
|
139
|
-
await savePageMetadata(config
|
|
141
|
+
await savePageMetadata(config, finalName, metadata);
|
|
140
142
|
|
|
141
143
|
res.status(201).json({ name: finalName, title: metadata.title });
|
|
142
144
|
} catch (err: unknown) {
|
|
@@ -149,7 +151,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
149
151
|
app.get('/api/pages/:name', async (req, res) => {
|
|
150
152
|
try {
|
|
151
153
|
const { name } = req.params;
|
|
152
|
-
const metadata = await loadPageMetadata(config
|
|
154
|
+
const metadata = await loadPageMetadata(config, name, config.requiredPagesFolders);
|
|
153
155
|
if (metadata) {
|
|
154
156
|
res.json(metadata);
|
|
155
157
|
} else {
|
|
@@ -200,7 +202,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
200
202
|
}
|
|
201
203
|
|
|
202
204
|
// Load existing metadata (or defaults)
|
|
203
|
-
const existing = await loadPageMetadata(config
|
|
205
|
+
const existing = await loadPageMetadata(config, name, config.requiredPagesFolders);
|
|
204
206
|
const metadata: PageMetadata = {
|
|
205
207
|
title: existing?.title ?? '',
|
|
206
208
|
categories: existing?.categories ?? [],
|
|
@@ -225,15 +227,23 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
225
227
|
// Promote required page to user folder if being unlocked/designed
|
|
226
228
|
if (metadata.mode !== 'locked') {
|
|
227
229
|
const userPagePath = path.join(config.pagesFolder, 'pages', name, 'page.html');
|
|
228
|
-
if (!(await checkIfExists(userPagePath))) {
|
|
229
|
-
|
|
230
|
+
if (!(await sp.checkIfExists(userPagePath))) {
|
|
231
|
+
// Read from required pages (package content, always local fs)
|
|
232
|
+
let html: string | undefined;
|
|
233
|
+
for (const folder of config.requiredPagesFolders) {
|
|
234
|
+
const candidate = path.join(folder, name, 'page.html');
|
|
235
|
+
if (await checkIfExists(candidate)) {
|
|
236
|
+
html = await loadFile(candidate);
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
230
240
|
if (html) {
|
|
231
|
-
await savePageState(config
|
|
241
|
+
await savePageState(config, name, html);
|
|
232
242
|
}
|
|
233
243
|
}
|
|
234
244
|
}
|
|
235
245
|
|
|
236
|
-
await savePageMetadata(config
|
|
246
|
+
await savePageMetadata(config, name, metadata);
|
|
237
247
|
res.json(metadata);
|
|
238
248
|
} catch (err: unknown) {
|
|
239
249
|
console.error(err);
|
|
@@ -252,7 +262,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
252
262
|
}
|
|
253
263
|
|
|
254
264
|
// Load existing metadata (user override → fallback .json → defaults)
|
|
255
|
-
let metadata = await loadPageMetadata(config
|
|
265
|
+
let metadata = await loadPageMetadata(config, name, config.requiredPagesFolders);
|
|
256
266
|
if (!metadata) {
|
|
257
267
|
metadata = {
|
|
258
268
|
title: '',
|
|
@@ -267,7 +277,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
267
277
|
}
|
|
268
278
|
|
|
269
279
|
metadata.pinned = pinned;
|
|
270
|
-
await savePageMetadata(config
|
|
280
|
+
await savePageMetadata(config, name, metadata);
|
|
271
281
|
res.json(metadata);
|
|
272
282
|
} catch (err: unknown) {
|
|
273
283
|
console.error(err);
|
|
@@ -281,7 +291,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
281
291
|
const { name } = req.params;
|
|
282
292
|
|
|
283
293
|
// Cannot delete required pages
|
|
284
|
-
if (
|
|
294
|
+
if (config.requiredPages.includes(name)) {
|
|
285
295
|
res.status(400).json({ error: `Cannot delete required page "${name}"` });
|
|
286
296
|
return;
|
|
287
297
|
}
|
|
@@ -289,13 +299,13 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
289
299
|
// Check if page exists (folder-based or legacy flat file)
|
|
290
300
|
const folderPath = path.join(config.pagesFolder, 'pages', name, 'page.html');
|
|
291
301
|
const flatPath = path.join(config.pagesFolder, `${name}.html`);
|
|
292
|
-
const exists = await checkIfExists(folderPath) || await checkIfExists(flatPath);
|
|
302
|
+
const exists = await sp.checkIfExists(folderPath) || await sp.checkIfExists(flatPath);
|
|
293
303
|
if (!exists) {
|
|
294
304
|
res.status(404).json({ error: `Page "${name}" not found` });
|
|
295
305
|
return;
|
|
296
306
|
}
|
|
297
307
|
|
|
298
|
-
await deletePage(config
|
|
308
|
+
await deletePage(config, name);
|
|
299
309
|
res.json({ deleted: true });
|
|
300
310
|
} catch (err: unknown) {
|
|
301
311
|
console.error(err);
|
|
@@ -303,11 +313,65 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
303
313
|
}
|
|
304
314
|
});
|
|
305
315
|
|
|
316
|
+
// Discover what a page contains (tables + files)
|
|
317
|
+
app.get('/api/pages/:name/contents', async (req, res) => {
|
|
318
|
+
try {
|
|
319
|
+
const { name } = req.params;
|
|
320
|
+
|
|
321
|
+
// Resolve page folder: user pages first, then required pages
|
|
322
|
+
let pageFolder: string | undefined;
|
|
323
|
+
const userFolder = path.join(config.pagesFolder, 'pages', name);
|
|
324
|
+
if (await sp.checkIfExists(path.join(userFolder, 'page.html'))) {
|
|
325
|
+
pageFolder = userFolder;
|
|
326
|
+
} else {
|
|
327
|
+
for (const folder of config.requiredPagesFolders) {
|
|
328
|
+
const candidate = path.join(folder, name);
|
|
329
|
+
if (await checkIfExists(path.join(candidate, 'page.html'))) {
|
|
330
|
+
pageFolder = candidate;
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (!pageFolder) {
|
|
337
|
+
res.status(404).json({ error: `Page "${name}" not found` });
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// List subdirectories, filtering out non-table entries
|
|
342
|
+
const EXCLUDED = new Set(['files']);
|
|
343
|
+
// Use provider for user folder, local fs for required pages
|
|
344
|
+
const isUserFolder = pageFolder === userFolder;
|
|
345
|
+
const subdirs = isUserFolder
|
|
346
|
+
? await sp.listFolders(pageFolder)
|
|
347
|
+
: await listFolders(pageFolder);
|
|
348
|
+
const tables = subdirs.filter(d => !EXCLUDED.has(d));
|
|
349
|
+
|
|
350
|
+
// Check if files/ exists and has entries
|
|
351
|
+
const filesDir = path.join(pageFolder, 'files');
|
|
352
|
+
let hasFiles = false;
|
|
353
|
+
const filesDirExists = isUserFolder
|
|
354
|
+
? await sp.checkIfExists(filesDir)
|
|
355
|
+
: await checkIfExists(filesDir);
|
|
356
|
+
if (filesDirExists) {
|
|
357
|
+
const entries = isUserFolder
|
|
358
|
+
? await sp.listFiles(filesDir)
|
|
359
|
+
: await listFiles(filesDir);
|
|
360
|
+
hasFiles = entries.length > 0;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
res.json({ tables, hasFiles });
|
|
364
|
+
} catch (err: unknown) {
|
|
365
|
+
console.error(err);
|
|
366
|
+
res.status(500).json({ error: (err as Error).message });
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
306
370
|
// Copy a page to a new name
|
|
307
371
|
app.post('/api/pages/:name/copy', async (req, res) => {
|
|
308
372
|
try {
|
|
309
373
|
const sourceName = req.params.name;
|
|
310
|
-
const { name: targetName, title, categories } = req.body;
|
|
374
|
+
const { name: targetName, title, categories, copyTables, copyFiles } = req.body;
|
|
311
375
|
|
|
312
376
|
// Validate target name
|
|
313
377
|
if (!targetName || typeof targetName !== 'string') {
|
|
@@ -329,10 +393,14 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
329
393
|
// Check source exists (user pages → required pages)
|
|
330
394
|
const sourceFolderPath = path.join(config.pagesFolder, 'pages', sourceName, 'page.html');
|
|
331
395
|
const sourceFlatPath = path.join(config.pagesFolder, `${sourceName}.html`);
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
396
|
+
let sourceRequiredPath: string | undefined;
|
|
397
|
+
for (const folder of config.requiredPagesFolders) {
|
|
398
|
+
const candidate = path.join(folder, sourceName, 'page.html');
|
|
399
|
+
if (await checkIfExists(candidate)) { sourceRequiredPath = candidate; break; }
|
|
400
|
+
}
|
|
401
|
+
const sourceExists = await sp.checkIfExists(sourceFolderPath)
|
|
402
|
+
|| await sp.checkIfExists(sourceFlatPath)
|
|
403
|
+
|| !!sourceRequiredPath;
|
|
336
404
|
if (!sourceExists) {
|
|
337
405
|
res.status(404).json({ error: `Source page "${sourceName}" not found` });
|
|
338
406
|
return;
|
|
@@ -341,22 +409,26 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
341
409
|
// Check target doesn't already exist
|
|
342
410
|
const targetFolderPath = path.join(config.pagesFolder, 'pages', targetName, 'page.html');
|
|
343
411
|
const targetFlatPath = path.join(config.pagesFolder, `${targetName}.html`);
|
|
344
|
-
if (await checkIfExists(targetFolderPath) || await checkIfExists(targetFlatPath)) {
|
|
412
|
+
if (await sp.checkIfExists(targetFolderPath) || await sp.checkIfExists(targetFlatPath)) {
|
|
345
413
|
res.status(409).json({ error: `Page "${targetName}" already exists` });
|
|
346
414
|
return;
|
|
347
415
|
}
|
|
348
416
|
|
|
349
417
|
await copyPage(
|
|
350
|
-
config
|
|
418
|
+
config,
|
|
351
419
|
sourceName,
|
|
352
420
|
targetName,
|
|
353
421
|
typeof title === 'string' ? title : '',
|
|
354
422
|
Array.isArray(categories) ? categories : [],
|
|
355
|
-
config.
|
|
423
|
+
config.requiredPagesFolders,
|
|
424
|
+
{
|
|
425
|
+
copyTables: copyTables === true,
|
|
426
|
+
copyFiles: copyFiles !== false, // default true
|
|
427
|
+
}
|
|
356
428
|
);
|
|
357
429
|
|
|
358
430
|
// Return the new page metadata
|
|
359
|
-
const metadata = await loadPageMetadata(config
|
|
431
|
+
const metadata = await loadPageMetadata(config, targetName);
|
|
360
432
|
res.status(201).json({ name: targetName, ...metadata });
|
|
361
433
|
} catch (err: unknown) {
|
|
362
434
|
console.error(err);
|
|
@@ -366,7 +438,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
366
438
|
|
|
367
439
|
// Define a route to return settings
|
|
368
440
|
app.get('/api/settings', async (req, res) => {
|
|
369
|
-
const settings = await loadSettings(config
|
|
441
|
+
const settings = await loadSettings(config);
|
|
370
442
|
const providers = PROVIDERS.map(p => ({ name: p.name, builderModels: p.builderModels, chatModels: p.chatModels }));
|
|
371
443
|
res.json({...settings, providers});
|
|
372
444
|
});
|
|
@@ -387,7 +459,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
387
459
|
}
|
|
388
460
|
|
|
389
461
|
// Save settings
|
|
390
|
-
await saveSettings(config
|
|
462
|
+
await saveSettings(config, settings);
|
|
391
463
|
res.redirect('/builder');
|
|
392
464
|
} catch (err: unknown) {
|
|
393
465
|
console.error(err);
|
|
@@ -397,7 +469,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
397
469
|
|
|
398
470
|
// Define a route to generate an image
|
|
399
471
|
app.post('/api/generate/image', async (req, res) => {
|
|
400
|
-
await requiresSettings(res, config
|
|
472
|
+
await requiresSettings(res, config, async (settings) => {
|
|
401
473
|
const { prompt, shape, style } = req.body;
|
|
402
474
|
const builder = getModelEntry(settings, 'builder');
|
|
403
475
|
const { configuration, imageQuality, provider } = builder;
|
|
@@ -414,9 +486,9 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
414
486
|
|
|
415
487
|
// Define a route to generate a completion using chain-of-thought
|
|
416
488
|
app.post('/api/generate/completion', async (req, res) => {
|
|
417
|
-
await requiresSettings(res, config
|
|
489
|
+
await requiresSettings(res, config, async (settings) => {
|
|
418
490
|
const { prompt, temperature } = req.body;
|
|
419
|
-
const completePrompt = await createCompletePrompt(config
|
|
491
|
+
const completePrompt = await createCompletePrompt(config, 'chat', req.body.model);
|
|
420
492
|
const response = await chainOfThought({ question: prompt, temperature, completePrompt });
|
|
421
493
|
if (response.completed) {
|
|
422
494
|
res.json(response.value ?? {});
|
|
@@ -428,16 +500,18 @@ export function useApiRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
428
500
|
});
|
|
429
501
|
|
|
430
502
|
// Brainstorm endpoint
|
|
503
|
+
if (!customizer || customizer.isEnabled('brainstorm'))
|
|
431
504
|
app.post('/api/brainstorm', async (req, res) => {
|
|
432
|
-
await requiresSettings(res, config
|
|
505
|
+
await requiresSettings(res, config, async (settings) => {
|
|
433
506
|
const { context, messages } = req.body;
|
|
434
|
-
const completePrompt = await createCompletePrompt(config
|
|
507
|
+
const completePrompt = await createCompletePrompt(config, 'chat');
|
|
435
508
|
|
|
509
|
+
const productName = customizer?.productName ?? 'SynthOS';
|
|
436
510
|
const system: { role: 'system'; content: string } = {
|
|
437
511
|
role: 'system',
|
|
438
|
-
content: `You are a creative brainstorming assistant for
|
|
439
|
-
|
|
440
|
-
The user is brainstorming — exploring ideas before building. Be concise, creative, and collaborative.
|
|
512
|
+
content: `You are a creative brainstorming assistant for ${productName}, a tool that builds pages through conversation.
|
|
513
|
+
${productName} is like a WIKI for vibe coding. Each page has a chat panel and a viewer panel. They are vibe coding what's displayed in that viewer panel. They can then save that as a page.
|
|
514
|
+
The user is brainstorming — exploring ideas before building. Be concise, creative, and collaborative.
|
|
441
515
|
They may say that they want to build an app or page that does XYZ but they're talking about what they expect to see in the viewer panel.
|
|
442
516
|
The goal is to help them generate a prompt for the builder that captures their vision, along with suggestions for next steps.
|
|
443
517
|
Suggest concrete approaches when you can, not complex visions for some ellaborate app.
|
|
@@ -448,14 +522,14 @@ ${context}
|
|
|
448
522
|
|
|
449
523
|
<INSTRUCTIONS>
|
|
450
524
|
Look at the <CHAT_HISTORY> and if it's empty it's the start of a new idea. Simply greet them and ask them what they're thinking of building. Suggestions could be help me decide, etc.
|
|
451
|
-
If you see a conversation between
|
|
525
|
+
If you see a conversation between ${productName} and the User. Asses what they're building and ask them what they'd like help with. Maybe offer a few good next steps.
|
|
452
526
|
|
|
453
|
-
|
|
527
|
+
${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.
|
|
454
528
|
|
|
455
529
|
You MUST return your response as a JSON object with exactly these fields:
|
|
456
530
|
{
|
|
457
531
|
"response": "Your conversational reply — explanations, options, suggestions. Markdown OK.",
|
|
458
|
-
"prompt": "A clean, actionable instruction ready to paste into
|
|
532
|
+
"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.",
|
|
459
533
|
"suggestions": ["Short clickable option A", "Short clickable option B", "Short clickable option C"]
|
|
460
534
|
}
|
|
461
535
|
|
|
@@ -495,12 +569,12 @@ Return ONLY the JSON object.`};
|
|
|
495
569
|
});
|
|
496
570
|
|
|
497
571
|
// Define a route for running configured scripts
|
|
572
|
+
if (!customizer || customizer.isEnabled('scripts'))
|
|
498
573
|
app.post('/api/scripts/:id', async (req, res) => {
|
|
499
|
-
await requiresSettings(res, config
|
|
574
|
+
await requiresSettings(res, config, async (settings) => {
|
|
500
575
|
const { id } = req.params;
|
|
501
|
-
const pagesFolder = config.pagesFolder;
|
|
502
576
|
const scriptId = id;
|
|
503
|
-
const response = await executeScript({ pagesFolder, scriptId, variables: req.body });
|
|
577
|
+
const response = await executeScript({ pagesFolder: config.pagesFolder, scriptId, variables: req.body });
|
|
504
578
|
if (response.completed) {
|
|
505
579
|
// Return the response as text
|
|
506
580
|
const value = (response.value?.output ?? (response.value?.errors ?? []).join('\n')).trim();
|
|
@@ -516,14 +590,20 @@ Return ONLY the JSON object.`};
|
|
|
516
590
|
// Return theme info as a self-executing JS script
|
|
517
591
|
app.get('/api/theme-info.js', async (req, res) => {
|
|
518
592
|
try {
|
|
519
|
-
const settings = await loadSettings(config
|
|
593
|
+
const settings = await loadSettings(config);
|
|
520
594
|
const themeName = settings.theme ?? 'nebula-dusk';
|
|
521
595
|
const info = await loadThemeInfo(themeName, config);
|
|
522
596
|
if (!info) {
|
|
523
597
|
res.status(404).send(`// Theme info for "${themeName}" not found`);
|
|
524
598
|
return;
|
|
525
599
|
}
|
|
526
|
-
const
|
|
600
|
+
const themeVersion = await loadThemeVersion(themeName, config);
|
|
601
|
+
const payload = { ...info, name: themeName, version: themeVersion };
|
|
602
|
+
let js = `window.themeInfo=${JSON.stringify(payload)};document.documentElement.classList.add(window.themeInfo.mode+"-mode");`;
|
|
603
|
+
if (themeVersion >= 3) {
|
|
604
|
+
js += `document.documentElement.classList.add(${JSON.stringify(themeName)});`;
|
|
605
|
+
}
|
|
606
|
+
js += `document.documentElement.setAttribute("data-toolbar",${JSON.stringify(settings.toolbarPosition || 'left')});`;
|
|
527
607
|
res.set('Content-Type', 'application/javascript');
|
|
528
608
|
res.send(js);
|
|
529
609
|
} catch (err: unknown) {
|
|
@@ -540,21 +620,18 @@ Return ONLY the JSON object.`};
|
|
|
540
620
|
res.status(400).send('// Missing page query parameter');
|
|
541
621
|
return;
|
|
542
622
|
}
|
|
543
|
-
const metadata = await loadPageMetadata(config
|
|
623
|
+
const metadata = await loadPageMetadata(config, page, config.requiredPagesFolders);
|
|
544
624
|
const mode = metadata?.mode ?? 'unlocked';
|
|
545
625
|
const title = metadata?.title ?? '';
|
|
546
626
|
const categories = metadata?.categories ?? [];
|
|
547
|
-
const
|
|
627
|
+
const isRequiredPage = config.requiredPages.includes(page);
|
|
628
|
+
const productName = customizer?.productName ?? 'SynthOS';
|
|
629
|
+
const info = JSON.stringify({ name: page, mode, latestPageVersion: PAGE_VERSION, title, categories, isRequiredPage, productName });
|
|
548
630
|
const js = [
|
|
549
631
|
`window.pageInfo=${info};`,
|
|
550
632
|
`if(window.pageInfo.mode==="locked"){`,
|
|
551
633
|
`document.addEventListener("DOMContentLoaded",function(){`,
|
|
552
634
|
`var f=document.getElementById("chatForm");if(f)f.style.display="none";`,
|
|
553
|
-
`var s=document.getElementById("saveLink");if(s)s.textContent="Copy";`,
|
|
554
|
-
`var r=document.getElementById("resetLink");if(r){`,
|
|
555
|
-
`var c=r.cloneNode(true);c.textContent="Reload";`,
|
|
556
|
-
`c.addEventListener("click",function(e){e.preventDefault();window.location.href=window.location.pathname;});`,
|
|
557
|
-
`r.parentNode.replaceChild(c,r);}`,
|
|
558
635
|
`});`,
|
|
559
636
|
`}`,
|
|
560
637
|
].join('');
|
|
@@ -570,7 +647,7 @@ Return ONLY the JSON object.`};
|
|
|
570
647
|
// Return the current theme as CSS
|
|
571
648
|
app.get('/api/theme.css', async (req, res) => {
|
|
572
649
|
try {
|
|
573
|
-
const settings = await loadSettings(config
|
|
650
|
+
const settings = await loadSettings(config);
|
|
574
651
|
const themeName = settings.theme ?? 'nebula-dusk';
|
|
575
652
|
const css = await loadTheme(themeName, config);
|
|
576
653
|
if (!css) {
|
|
@@ -604,8 +681,8 @@ Return ONLY the JSON object.`};
|
|
|
604
681
|
res.status(400).send('// Invalid version parameter');
|
|
605
682
|
return;
|
|
606
683
|
}
|
|
607
|
-
const scriptPath =
|
|
608
|
-
if (!
|
|
684
|
+
const scriptPath = await findFileInFolders(config.staticFilesFolders, `page.v${v}.js`);
|
|
685
|
+
if (!scriptPath) {
|
|
609
686
|
res.status(404).send(`// page-v${v}.js not found`);
|
|
610
687
|
return;
|
|
611
688
|
}
|
|
@@ -627,8 +704,8 @@ Return ONLY the JSON object.`};
|
|
|
627
704
|
res.status(400).send('// Invalid version parameter');
|
|
628
705
|
return;
|
|
629
706
|
}
|
|
630
|
-
const scriptPath =
|
|
631
|
-
if (!
|
|
707
|
+
const scriptPath = await findFileInFolders(config.staticFilesFolders, `helpers.v${v}.js`);
|
|
708
|
+
if (!scriptPath) {
|
|
632
709
|
res.status(404).send(`// helpers-v${v}.js not found`);
|
|
633
710
|
return;
|
|
634
711
|
}
|
|
@@ -654,7 +731,7 @@ Return ONLY the JSON object.`};
|
|
|
654
731
|
// Return user's configured services (API keys masked)
|
|
655
732
|
app.get('/api/services', async (_req, res) => {
|
|
656
733
|
try {
|
|
657
|
-
const settings = await loadSettings(config
|
|
734
|
+
const settings = await loadSettings(config);
|
|
658
735
|
const services = settings.services ?? {};
|
|
659
736
|
const masked: Record<string, { enabled: boolean; hasKey: boolean }> = {};
|
|
660
737
|
for (const [id, cfg] of Object.entries(services)) {
|
|
@@ -674,7 +751,7 @@ Return ONLY the JSON object.`};
|
|
|
674
751
|
app.post('/api/services', async (req, res) => {
|
|
675
752
|
try {
|
|
676
753
|
const incoming = req.body as ServicesConfig;
|
|
677
|
-
const settings = await loadSettings(config
|
|
754
|
+
const settings = await loadSettings(config);
|
|
678
755
|
const existing = settings.services ?? {};
|
|
679
756
|
|
|
680
757
|
// Build merged config — empty apiKey means "keep existing"
|
|
@@ -695,7 +772,7 @@ Return ONLY the JSON object.`};
|
|
|
695
772
|
}
|
|
696
773
|
}
|
|
697
774
|
|
|
698
|
-
await saveSettings(config
|
|
775
|
+
await saveSettings(config, { services: merged });
|
|
699
776
|
res.json({ saved: true });
|
|
700
777
|
} catch (err: unknown) {
|
|
701
778
|
console.error(err);
|
|
@@ -707,6 +784,7 @@ Return ONLY the JSON object.`};
|
|
|
707
784
|
// Web Search (Brave Search API)
|
|
708
785
|
// -----------------------------------------------------------------------
|
|
709
786
|
|
|
787
|
+
if (!customizer || customizer.isEnabled('search'))
|
|
710
788
|
app.post('/api/search/web', async (req, res) => {
|
|
711
789
|
try {
|
|
712
790
|
const { query, count, country, freshness } = req.body;
|
|
@@ -715,7 +793,7 @@ Return ONLY the JSON object.`};
|
|
|
715
793
|
return;
|
|
716
794
|
}
|
|
717
795
|
|
|
718
|
-
const settings = await loadSettings(config
|
|
796
|
+
const settings = await loadSettings(config);
|
|
719
797
|
const braveConfig = settings.connectors?.['brave-search'] ?? settings.services?.['brave-search'];
|
|
720
798
|
if (!braveConfig || !braveConfig.enabled || !braveConfig.apiKey) {
|
|
721
799
|
res.status(400).json({ error: 'Brave Search is not configured or not enabled. Add your API key in Settings > Services.' });
|
|
@@ -761,7 +839,7 @@ Return ONLY the JSON object.`};
|
|
|
761
839
|
const { name } = req.params;
|
|
762
840
|
|
|
763
841
|
// Load current metadata
|
|
764
|
-
const metadata = await loadPageMetadata(config
|
|
842
|
+
const metadata = await loadPageMetadata(config, name, config.requiredPagesFolders);
|
|
765
843
|
if (!metadata) {
|
|
766
844
|
res.status(404).json({ error: `Page "${name}" not found` });
|
|
767
845
|
return;
|
|
@@ -781,32 +859,37 @@ Return ONLY the JSON object.`};
|
|
|
781
859
|
}
|
|
782
860
|
|
|
783
861
|
// Run LLM-based migration
|
|
784
|
-
const completePrompt = await createCompletePrompt(config
|
|
862
|
+
const completePrompt = await createCompletePrompt(config, 'builder');
|
|
785
863
|
const migratedHtml = await migratePage(html, currentVersion, PAGE_VERSION, completePrompt);
|
|
786
864
|
|
|
787
865
|
// Save upgraded HTML to v2 folder structure
|
|
788
|
-
await savePageState(config
|
|
866
|
+
await savePageState(config, name, migratedHtml);
|
|
789
867
|
|
|
790
868
|
// Backup original page to .migrated/ before overwriting
|
|
791
869
|
const migratedFolder = path.join(config.pagesFolder, '.migrated');
|
|
792
870
|
|
|
793
|
-
// Handle legacy flat file (
|
|
871
|
+
// Handle legacy flat file (<localFolder>/pagename.html)
|
|
794
872
|
const flatPath = path.join(config.pagesFolder, `${name}.html`);
|
|
795
|
-
if (await checkIfExists(flatPath)) {
|
|
796
|
-
await
|
|
797
|
-
await
|
|
873
|
+
if (await sp.checkIfExists(flatPath)) {
|
|
874
|
+
await sp.ensureFolderExists(migratedFolder);
|
|
875
|
+
const flatData = await sp.loadBuffer(flatPath);
|
|
876
|
+
await sp.saveBuffer(path.join(migratedFolder, `${name}.html`), flatData);
|
|
877
|
+
await sp.deleteFile(flatPath);
|
|
798
878
|
}
|
|
799
879
|
|
|
800
|
-
// Handle folder-based page (
|
|
880
|
+
// Handle folder-based page (<localFolder>/pages/name/)
|
|
801
881
|
const folderPath = path.join(config.pagesFolder, 'pages', name);
|
|
802
|
-
if (await checkIfExists(folderPath)) {
|
|
803
|
-
await copyFolderRecursive(folderPath, path.join(migratedFolder, name));
|
|
882
|
+
if (await sp.checkIfExists(folderPath)) {
|
|
883
|
+
await sp.copyFolderRecursive(folderPath, path.join(migratedFolder, name));
|
|
804
884
|
}
|
|
805
885
|
|
|
886
|
+
// Clear stale version files (undo snapshots from the old page version)
|
|
887
|
+
await clearVersions(config, name);
|
|
888
|
+
|
|
806
889
|
// Update metadata
|
|
807
890
|
metadata.pageVersion = PAGE_VERSION;
|
|
808
891
|
metadata.lastModified = new Date().toISOString();
|
|
809
|
-
await savePageMetadata(config
|
|
892
|
+
await savePageMetadata(config, name, metadata);
|
|
810
893
|
|
|
811
894
|
res.json({ upgraded: true, fromVersion: currentVersion, toVersion: PAGE_VERSION });
|
|
812
895
|
} catch (err: unknown) {
|
|
@@ -822,21 +905,27 @@ Return ONLY the JSON object.`};
|
|
|
822
905
|
|
|
823
906
|
// Try user pages folder first, then required pages
|
|
824
907
|
const userPageDir = path.join(config.pagesFolder, 'pages', name);
|
|
825
|
-
|
|
908
|
+
let requiredPageDir: string | undefined;
|
|
909
|
+
for (const folder of config.requiredPagesFolders) {
|
|
910
|
+
if (await checkIfExists(path.join(folder, name, 'page.html'))) { // package content, local fs
|
|
911
|
+
requiredPageDir = path.join(folder, name);
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
826
915
|
let sourceDir: string | null = null;
|
|
827
916
|
|
|
828
|
-
if (await checkIfExists(path.join(userPageDir, 'page.html'))) {
|
|
917
|
+
if (await sp.checkIfExists(path.join(userPageDir, 'page.html'))) {
|
|
829
918
|
sourceDir = userPageDir;
|
|
830
|
-
} else if (
|
|
919
|
+
} else if (requiredPageDir) {
|
|
831
920
|
// For required pages, create a temp-like zip with just the HTML
|
|
832
921
|
const zip = new AdmZip();
|
|
833
|
-
const html = await loadFile(
|
|
922
|
+
const html = await loadFile(path.join(requiredPageDir, 'page.html'));
|
|
834
923
|
zip.addFile(`${name}/page.html`, Buffer.from(html, 'utf-8'));
|
|
835
924
|
|
|
836
925
|
// Include page.json if it exists
|
|
837
|
-
const
|
|
838
|
-
if (await checkIfExists(
|
|
839
|
-
const meta = await loadFile(
|
|
926
|
+
const metaPath = path.join(requiredPageDir, 'page.json');
|
|
927
|
+
if (await checkIfExists(metaPath)) {
|
|
928
|
+
const meta = await loadFile(metaPath);
|
|
840
929
|
zip.addFile(`${name}/page.json`, Buffer.from(meta, 'utf-8'));
|
|
841
930
|
}
|
|
842
931
|
|
|
@@ -863,4 +952,39 @@ Return ONLY the JSON object.`};
|
|
|
863
952
|
res.status(500).json({ error: (err as Error).message });
|
|
864
953
|
}
|
|
865
954
|
});
|
|
955
|
+
|
|
956
|
+
// Ask a question about a page (with full page HTML context)
|
|
957
|
+
app.post('/api/pages/:name/ask', async (req, res) => {
|
|
958
|
+
await requiresSettings(res, config, async (settings) => {
|
|
959
|
+
const { name } = req.params;
|
|
960
|
+
const { question } = req.body;
|
|
961
|
+
if (typeof question !== 'string' || !question.trim()) {
|
|
962
|
+
res.status(400).json({ error: 'question is required' });
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// Load the page HTML
|
|
967
|
+
const html = await loadPageWithFallback(name, config, false);
|
|
968
|
+
if (!html) {
|
|
969
|
+
res.status(404).json({ error: `Page "${name}" not found` });
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Create completion (uses 'chat' model, not 'builder')
|
|
974
|
+
const complete = await createCompletePrompt(config, 'chat', req.body.model);
|
|
975
|
+
|
|
976
|
+
const system = {
|
|
977
|
+
role: 'system' as const,
|
|
978
|
+
content: `You are a helpful assistant. The user will ask questions about a web page. Answer based on the page content provided.\n\n<PAGE_HTML>\n${html}`
|
|
979
|
+
};
|
|
980
|
+
const prompt = { role: 'user' as const, content: question };
|
|
981
|
+
|
|
982
|
+
const result = await complete({ system, prompt });
|
|
983
|
+
if (result.completed) {
|
|
984
|
+
res.json({ answer: result.value });
|
|
985
|
+
} else {
|
|
986
|
+
res.status(500).json({ error: result.error?.message || 'Completion failed' });
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
});
|
|
866
990
|
}
|