synthos 0.8.0 → 0.9.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 +1822 -0
- package/default-pages/{neon_asteroids.json → neon_asteroids/page.json} +3 -3
- package/default-pages/{oregon_trail.html → oregon_trail/page.html} +14 -12
- 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} +14 -11
- 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.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 +57 -0
- package/dist/customizer/Customizer.d.ts.map +1 -0
- package/dist/customizer/Customizer.js +124 -0
- package/dist/customizer/Customizer.js.map +1 -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.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +10 -6
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +96 -113
- 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 +30 -7
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +177 -55
- package/dist/pages.js.map +1 -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 +5 -2
- package/dist/service/useAgentRoutes.js.map +1 -1
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +237 -136
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.js +6 -6
- package/dist/service/useConnectorRoutes.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.map +1 -1
- package/dist/service/usePageRoutes.js +648 -67
- 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 +104 -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 +1 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +1 -0
- package/dist/settings.js.map +1 -1
- 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 +28 -15
- 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 +151 -0
- package/src/customizer/index.ts +5 -0
- package/src/files.ts +57 -0
- package/src/index.ts +2 -1
- package/src/init.ts +137 -123
- 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 +176 -54
- package/src/service/server.ts +36 -9
- package/src/service/transformPage.ts +557 -326
- package/src/service/useAgentRoutes.ts +7 -2
- package/src/service/useApiRoutes.ts +150 -41
- package/src/service/useConnectorRoutes.ts +7 -7
- package/src/service/useFileRoutes.ts +127 -0
- package/src/service/usePageRoutes.ts +720 -73
- package/src/service/useSharedDataRoutes.ts +106 -0
- package/src/service/useSharedFileRoutes.ts +126 -0
- package/src/settings.ts +2 -0
- package/src/synthos-cli.ts +4 -3
- package/src/themes.ts +25 -14
- 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 +8 -8
- 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/agents/a2a/a2aProvider.d.ts +0 -3
- package/dist/agents/discovery.d.ts +0 -30
- package/dist/agents/openclaw/openclawProvider.d.ts +0 -3
- package/dist/agents/types.d.ts +0 -64
- package/dist/connectors/index.d.ts +0 -3
- package/dist/connectors/types.d.ts +0 -84
- package/dist/index.d.ts +0 -7
- package/dist/migrations.d.ts +0 -12
- package/dist/models/chainOfThought.d.ts +0 -12
- package/dist/models/fireworksai.d.ts +0 -30
- package/dist/models/logCompletePrompt.d.ts +0 -3
- package/dist/models/providers.d.ts +0 -8
- package/dist/models/utils.d.ts +0 -6
- package/dist/scripts.d.ts +0 -15
- package/dist/service/createCompletePrompt.d.ts +0 -5
- package/dist/service/debugLog.d.ts +0 -11
- package/dist/service/generateImage.d.ts +0 -32
- package/dist/service/index.d.ts +0 -8
- package/dist/service/modelInstructions.d.ts +0 -7
- package/dist/service/requiresSettings.d.ts +0 -3
- package/dist/service/server.d.ts +0 -4
- package/dist/service/useApiRoutes.d.ts +0 -4
- package/dist/service/useConnectorRoutes.d.ts +0 -4
- package/dist/service/useDataRoutes.d.ts +0 -4
- 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/dist/service/usePageRoutes.d.ts +0 -5
- package/dist/synthos-cli.d.ts +0 -2
- 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
|
@@ -0,0 +1,1290 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
// Fallback navigation — overridden in section 10 with unsaved-changes guard
|
|
3
|
+
if (!window.__synthOSNavigateTo) {
|
|
4
|
+
window.__synthOSNavigateTo = function(url) { window.location.href = url; };
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
if (window.__synthOSChatPanel) return;
|
|
8
|
+
window.__synthOSChatPanel = true;
|
|
9
|
+
|
|
10
|
+
// Product name — used for branding throughout the page script
|
|
11
|
+
var pn = (window.pageInfo && window.pageInfo.productName) ? window.pageInfo.productName : 'SynthOS';
|
|
12
|
+
|
|
13
|
+
// 0. First-run greeting — replace default greeting when ?firstRun=true
|
|
14
|
+
(function() {
|
|
15
|
+
var params = new URLSearchParams(window.location.search);
|
|
16
|
+
if (params.get('firstRun') !== 'true') return;
|
|
17
|
+
var greeting = document.getElementById('defaultGreeting');
|
|
18
|
+
if (!greeting) return;
|
|
19
|
+
greeting.innerHTML =
|
|
20
|
+
'<p><strong>Welcome to ' + pn + '!</strong></p>' +
|
|
21
|
+
'<p>You\'re all set up and ready to start creating. This is the <strong>Builder</strong> — your main workspace. Just type what you want to build into the chat and ' + pn + ' will generate it for you as a live, interactive page.</p>' +
|
|
22
|
+
'<p>You can create just about anything: dashboards, tools, games, visualizations, forms, calculators — if it can be expressed as a web page, you can build it here through conversation.</p>' +
|
|
23
|
+
'<p><strong>How pages work:</strong></p>' +
|
|
24
|
+
'<ul>' +
|
|
25
|
+
'<li>Each creation lives on its own <strong>page</strong>. When you save, it gets a name and becomes part of your collection.</li>' +
|
|
26
|
+
'<li>The <strong>Pages</strong> button (in the link bar above) takes you to the <strong>Pages Gallery</strong> where you can browse, open, and manage all your saved creations.</li>' +
|
|
27
|
+
'</ul>' +
|
|
28
|
+
'<p><strong>Key actions:</strong></p>' +
|
|
29
|
+
'<ul>' +
|
|
30
|
+
'<li><strong>Save</strong> — saves your current page. You\'ll be prompted for a name the first time. <em>Save often!</em> Your work only persists when you save it.</li>' +
|
|
31
|
+
'<li><strong>Reset</strong> — clears the current page back to a blank slate. Useful when you want to start fresh on something new.</li>' +
|
|
32
|
+
'</ul>' +
|
|
33
|
+
'<p>When you\'re ready, we recommend trying the <a href="/solar_tutorial">Solar Tutorial</a> — it\'s a guided walkthrough that will show you the basics of creating with ' + pn + ' step by step.</p>' +
|
|
34
|
+
'<p>Have fun building!</p>';
|
|
35
|
+
})();
|
|
36
|
+
|
|
37
|
+
// 1. Themed tooltips for chat panel controls
|
|
38
|
+
(function() {
|
|
39
|
+
var style = document.createElement('style');
|
|
40
|
+
style.textContent =
|
|
41
|
+
'.synthos-tooltip {' +
|
|
42
|
+
'position: fixed;' +
|
|
43
|
+
'padding: 6px 10px;' +
|
|
44
|
+
'background: var(--bg-tertiary, #0f0f23);' +
|
|
45
|
+
'color: var(--text-secondary, #b794f6);' +
|
|
46
|
+
'border: 1px solid var(--border-color, rgba(138,43,226,0.3));' +
|
|
47
|
+
'border-radius: 6px;' +
|
|
48
|
+
'font-size: 12px;' +
|
|
49
|
+
'max-width: 150px;' +
|
|
50
|
+
'text-align: center;' +
|
|
51
|
+
'pointer-events: none;' +
|
|
52
|
+
'z-index: 10000;' +
|
|
53
|
+
'box-shadow: 0 2px 8px rgba(0,0,0,0.3);' +
|
|
54
|
+
'opacity: 0;' +
|
|
55
|
+
'transition: opacity 0.15s;' +
|
|
56
|
+
'}' +
|
|
57
|
+
'.synthos-tooltip.visible { opacity: 1; }';
|
|
58
|
+
document.head.appendChild(style);
|
|
59
|
+
|
|
60
|
+
var tip = document.createElement('div');
|
|
61
|
+
tip.className = 'synthos-tooltip';
|
|
62
|
+
document.body.appendChild(tip);
|
|
63
|
+
|
|
64
|
+
function show(el) {
|
|
65
|
+
tip.textContent = el.getAttribute('data-tooltip');
|
|
66
|
+
tip.style.display = 'block';
|
|
67
|
+
tip.classList.remove('visible');
|
|
68
|
+
var r = el.getBoundingClientRect();
|
|
69
|
+
var tw = tip.offsetWidth;
|
|
70
|
+
var left = r.left + (r.width / 2) - (tw / 2);
|
|
71
|
+
if (left < 4) left = 4;
|
|
72
|
+
if (left + tw > window.innerWidth - 4) left = window.innerWidth - tw - 4;
|
|
73
|
+
tip.style.left = left + 'px';
|
|
74
|
+
tip.style.top = (r.top - tip.offsetHeight - 6) + 'px';
|
|
75
|
+
void tip.offsetWidth;
|
|
76
|
+
tip.classList.add('visible');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function hide() {
|
|
80
|
+
tip.classList.remove('visible');
|
|
81
|
+
tip.style.display = 'none';
|
|
82
|
+
}
|
|
83
|
+
hide();
|
|
84
|
+
|
|
85
|
+
function attach(el, text) {
|
|
86
|
+
el.setAttribute('data-tooltip', text);
|
|
87
|
+
el.addEventListener('mouseenter', function() { show(el); });
|
|
88
|
+
el.addEventListener('mouseleave', hide);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
window.__synthOSTooltip = attach;
|
|
92
|
+
})();
|
|
93
|
+
|
|
94
|
+
var chatInput = document.getElementById('chatInput');
|
|
95
|
+
|
|
96
|
+
// 2. Form submit handler — fetch+JSON with attachment support
|
|
97
|
+
var chatForm = document.getElementById('chatForm');
|
|
98
|
+
if (chatForm) {
|
|
99
|
+
chatForm.addEventListener('submit', function(e) {
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
|
|
102
|
+
var ci = document.getElementById('chatInput');
|
|
103
|
+
var messageText = ci ? ci.value : '';
|
|
104
|
+
|
|
105
|
+
// Append any captured console errors to the outgoing message
|
|
106
|
+
var errors = window.__synthOSErrors;
|
|
107
|
+
if (errors && errors.length > 0 && messageText.trim()) {
|
|
108
|
+
messageText = messageText + '\n\nCONSOLE_ERRORS:\n' + errors.join('\n---\n');
|
|
109
|
+
window.__synthOSErrors = [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!messageText.trim()) return;
|
|
113
|
+
|
|
114
|
+
// Show overlay and disable inputs
|
|
115
|
+
var overlay = document.getElementById('loadingOverlay');
|
|
116
|
+
if (overlay) overlay.style.display = 'flex';
|
|
117
|
+
setTimeout(function() {
|
|
118
|
+
if (ci) ci.disabled = true;
|
|
119
|
+
var sb = document.querySelector('.chat-send-btn');
|
|
120
|
+
if (sb) sb.disabled = true;
|
|
121
|
+
}, 50);
|
|
122
|
+
|
|
123
|
+
// Build JSON body with optional attachments
|
|
124
|
+
var body = { message: messageText };
|
|
125
|
+
var attachments = window.__synthOSAttachments;
|
|
126
|
+
if (attachments && attachments.length > 0) {
|
|
127
|
+
body.attachments = attachments.map(function(a) {
|
|
128
|
+
return { mediaType: a.mediaType, data: a.data, name: a.name };
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fetch(window.location.pathname, {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: { 'Content-Type': 'application/json' },
|
|
135
|
+
body: JSON.stringify(body)
|
|
136
|
+
})
|
|
137
|
+
.then(function(res) { return res.text(); })
|
|
138
|
+
.then(function(html) {
|
|
139
|
+
// Mark page as dirty (unsaved changes) for required-page prompt
|
|
140
|
+
window.__synthOSPageDirty = true;
|
|
141
|
+
// Reset guards so page-v3.js re-initializes on the new DOM
|
|
142
|
+
window.__synthOSChatPanel = false;
|
|
143
|
+
window.__synthOSNavigateTo = null;
|
|
144
|
+
document.open();
|
|
145
|
+
document.write(html);
|
|
146
|
+
document.close();
|
|
147
|
+
})
|
|
148
|
+
.catch(function(err) {
|
|
149
|
+
console.error('Submit failed:', err);
|
|
150
|
+
if (overlay) overlay.style.display = 'none';
|
|
151
|
+
if (ci) ci.disabled = false;
|
|
152
|
+
var sb = document.querySelector('.chat-send-btn');
|
|
153
|
+
if (sb) sb.disabled = false;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Clear attachments after submit
|
|
157
|
+
window.__synthOSAttachments = [];
|
|
158
|
+
var pillsContainer = document.querySelector('.attachment-pills');
|
|
159
|
+
if (pillsContainer) pillsContainer.innerHTML = '';
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 2b. Enter submits, Shift+Enter adds newline
|
|
164
|
+
(function() {
|
|
165
|
+
var ci = document.getElementById('chatInput');
|
|
166
|
+
if (!ci) return;
|
|
167
|
+
ci.addEventListener('keydown', function(e) {
|
|
168
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
var form = document.getElementById('chatForm');
|
|
171
|
+
if (form) form.requestSubmit();
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
})();
|
|
175
|
+
|
|
176
|
+
// 3. Save modal — themed modal with title, categories, greeting
|
|
177
|
+
(function() {
|
|
178
|
+
|
|
179
|
+
// Detect if current page is a Builders or System page (start with blank fields)
|
|
180
|
+
var isBuilder = window.pageInfo && Array.isArray(window.pageInfo.categories) &&
|
|
181
|
+
(window.pageInfo.categories.indexOf('Builders') !== -1 ||
|
|
182
|
+
window.pageInfo.categories.indexOf('System') !== -1);
|
|
183
|
+
|
|
184
|
+
// Original title for change detection
|
|
185
|
+
var originalTitle = (window.pageInfo && window.pageInfo.title) ? window.pageInfo.title : '';
|
|
186
|
+
|
|
187
|
+
// --- Create save modal ---
|
|
188
|
+
var modal = document.createElement('div');
|
|
189
|
+
modal.id = 'synthos-saveModal';
|
|
190
|
+
modal.className = 'modal-overlay';
|
|
191
|
+
modal.innerHTML =
|
|
192
|
+
'<div class="modal-content" style="max-width:480px;">' +
|
|
193
|
+
'<div class="modal-header">' +
|
|
194
|
+
'<span>Save Page</span>' +
|
|
195
|
+
'<button type="button" class="brainstorm-close-btn" id="synthos-saveCloseBtn">×</button>' +
|
|
196
|
+
'</div>' +
|
|
197
|
+
'<div class="modal-body" style="display:flex;flex-direction:column;gap:12px;padding:16px 20px;">' +
|
|
198
|
+
'<div>' +
|
|
199
|
+
'<label style="display:block;margin-bottom:4px;font-size:13px;color:var(--text-secondary);">Display Title <span style="color:var(--accent-primary);">*</span></label>' +
|
|
200
|
+
'<input type="text" id="synthos-saveTitleInput" class="brainstorm-input" placeholder="Enter a display title..." style="width:100%;box-sizing:border-box;">' +
|
|
201
|
+
'<div id="synthos-saveTitleError" style="color:#ff6b6b;font-size:12px;margin-top:4px;display:none;">Title is required</div>' +
|
|
202
|
+
'</div>' +
|
|
203
|
+
'<div>' +
|
|
204
|
+
'<label style="display:block;margin-bottom:4px;font-size:13px;color:var(--text-secondary);">Categories <span style="color:var(--accent-primary);">*</span></label>' +
|
|
205
|
+
'<input type="text" id="synthos-saveCategoriesInput" class="brainstorm-input" placeholder="e.g. Tool, Game, Utility" style="width:100%;box-sizing:border-box;">' +
|
|
206
|
+
'<div id="synthos-saveCategoriesError" style="color:#ff6b6b;font-size:12px;margin-top:4px;display:none;">At least one category is required</div>' +
|
|
207
|
+
'</div>' +
|
|
208
|
+
'<div>' +
|
|
209
|
+
'<label style="display:block;margin-bottom:4px;font-size:13px;color:var(--text-secondary);">Greeting <span style="font-size:11px;opacity:0.7;">(optional)</span></label>' +
|
|
210
|
+
'<input type="text" id="synthos-saveGreetingInput" class="brainstorm-input" placeholder="Available when title changes" style="width:100%;box-sizing:border-box;" disabled>' +
|
|
211
|
+
'<div style="font-size:11px;color:var(--text-secondary);margin-top:4px;opacity:0.7;" id="synthos-saveGreetingHint">Change the title to enable a custom greeting.</div>' +
|
|
212
|
+
'</div>' +
|
|
213
|
+
'</div>' +
|
|
214
|
+
'<div class="modal-footer" style="display:flex;justify-content:flex-end;gap:8px;padding:12px 20px;">' +
|
|
215
|
+
'<button type="button" class="brainstorm-send-btn" id="synthos-saveCancelBtn" style="background:transparent;border:1px solid var(--border-color);color:var(--text-secondary);">Cancel</button>' +
|
|
216
|
+
'<button type="button" class="brainstorm-send-btn" id="synthos-saveConfirmBtn">Save</button>' +
|
|
217
|
+
'</div>' +
|
|
218
|
+
'</div>';
|
|
219
|
+
document.body.appendChild(modal);
|
|
220
|
+
|
|
221
|
+
// --- Create error modal ---
|
|
222
|
+
var errorModal = document.createElement('div');
|
|
223
|
+
errorModal.id = 'synthos-errorModal';
|
|
224
|
+
errorModal.className = 'modal-overlay';
|
|
225
|
+
errorModal.innerHTML =
|
|
226
|
+
'<div class="modal-content" style="max-width:400px;">' +
|
|
227
|
+
'<div class="modal-header">' +
|
|
228
|
+
'<span>Error</span>' +
|
|
229
|
+
'<button type="button" class="brainstorm-close-btn" id="synthos-errorCloseBtn">×</button>' +
|
|
230
|
+
'</div>' +
|
|
231
|
+
'<div class="modal-body" style="padding:16px 20px;">' +
|
|
232
|
+
'<p id="synthos-errorMessage" style="margin:0;color:var(--text-primary);"></p>' +
|
|
233
|
+
'</div>' +
|
|
234
|
+
'<div class="modal-footer" style="display:flex;justify-content:flex-end;padding:12px 20px;">' +
|
|
235
|
+
'<button type="button" class="brainstorm-send-btn" id="synthos-errorOkBtn">OK</button>' +
|
|
236
|
+
'</div>' +
|
|
237
|
+
'</div>';
|
|
238
|
+
document.body.appendChild(errorModal);
|
|
239
|
+
|
|
240
|
+
// --- Element references ---
|
|
241
|
+
var titleInput = document.getElementById('synthos-saveTitleInput');
|
|
242
|
+
var categoriesInput = document.getElementById('synthos-saveCategoriesInput');
|
|
243
|
+
var greetingInput = document.getElementById('synthos-saveGreetingInput');
|
|
244
|
+
var greetingHint = document.getElementById('synthos-saveGreetingHint');
|
|
245
|
+
var titleError = document.getElementById('synthos-saveTitleError');
|
|
246
|
+
var categoriesError = document.getElementById('synthos-saveCategoriesError');
|
|
247
|
+
|
|
248
|
+
// --- Greeting enable/disable based on title change ---
|
|
249
|
+
titleInput.addEventListener('input', function() {
|
|
250
|
+
var changed = titleInput.value.trim() !== originalTitle;
|
|
251
|
+
greetingInput.disabled = !changed;
|
|
252
|
+
if (changed) {
|
|
253
|
+
greetingInput.placeholder = 'Enter a custom greeting...';
|
|
254
|
+
greetingHint.textContent = 'Replaces the initial Synthos greeting and removes chat history.';
|
|
255
|
+
} else {
|
|
256
|
+
greetingInput.placeholder = 'Available when title changes';
|
|
257
|
+
greetingInput.value = '';
|
|
258
|
+
greetingHint.textContent = 'Change the title to enable a custom greeting.';
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// --- Open modal ---
|
|
263
|
+
function openSaveModal() {
|
|
264
|
+
// Pre-fill fields (blank for Builder pages)
|
|
265
|
+
titleInput.value = isBuilder ? '' : originalTitle;
|
|
266
|
+
categoriesInput.value = isBuilder ? '' : (
|
|
267
|
+
(window.pageInfo && Array.isArray(window.pageInfo.categories))
|
|
268
|
+
? window.pageInfo.categories.join(', ')
|
|
269
|
+
: ''
|
|
270
|
+
);
|
|
271
|
+
greetingInput.value = '';
|
|
272
|
+
greetingInput.disabled = true;
|
|
273
|
+
greetingInput.placeholder = 'Available when title changes';
|
|
274
|
+
greetingHint.textContent = 'Change the title to enable a custom greeting.';
|
|
275
|
+
titleError.style.display = 'none';
|
|
276
|
+
categoriesError.style.display = 'none';
|
|
277
|
+
modal.classList.add('show');
|
|
278
|
+
titleInput.focus();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function closeSaveModal() {
|
|
282
|
+
modal.classList.remove('show');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function showError(msg) {
|
|
286
|
+
document.getElementById('synthos-errorMessage').textContent = msg;
|
|
287
|
+
errorModal.classList.add('show');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function closeError() {
|
|
291
|
+
errorModal.classList.remove('show');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// --- Submit ---
|
|
295
|
+
function submitSave() {
|
|
296
|
+
var title = titleInput.value.trim();
|
|
297
|
+
var cats = categoriesInput.value.trim();
|
|
298
|
+
var greeting = greetingInput.value.trim();
|
|
299
|
+
var valid = true;
|
|
300
|
+
|
|
301
|
+
// Validate
|
|
302
|
+
if (!title) {
|
|
303
|
+
titleError.style.display = 'block';
|
|
304
|
+
valid = false;
|
|
305
|
+
} else {
|
|
306
|
+
titleError.style.display = 'none';
|
|
307
|
+
}
|
|
308
|
+
if (!cats) {
|
|
309
|
+
categoriesError.style.display = 'block';
|
|
310
|
+
valid = false;
|
|
311
|
+
} else {
|
|
312
|
+
categoriesError.style.display = 'none';
|
|
313
|
+
}
|
|
314
|
+
if (!valid) return;
|
|
315
|
+
|
|
316
|
+
// Parse categories
|
|
317
|
+
var categories = cats.split(',').map(function(c) { return c.trim(); }).filter(Boolean);
|
|
318
|
+
|
|
319
|
+
// Disable button during save
|
|
320
|
+
var confirmBtn = document.getElementById('synthos-saveConfirmBtn');
|
|
321
|
+
confirmBtn.disabled = true;
|
|
322
|
+
confirmBtn.textContent = 'Saving...';
|
|
323
|
+
|
|
324
|
+
var body = { title: title, categories: categories };
|
|
325
|
+
if (greeting && !greetingInput.disabled) {
|
|
326
|
+
body.greeting = greeting;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
fetch(window.location.pathname + '/save', {
|
|
330
|
+
method: 'POST',
|
|
331
|
+
headers: { 'Content-Type': 'application/json' },
|
|
332
|
+
body: JSON.stringify(body)
|
|
333
|
+
})
|
|
334
|
+
.then(function(res) {
|
|
335
|
+
return res.json().then(function(data) {
|
|
336
|
+
return { ok: res.ok, data: data };
|
|
337
|
+
});
|
|
338
|
+
})
|
|
339
|
+
.then(function(result) {
|
|
340
|
+
if (result.ok && result.data.redirect) {
|
|
341
|
+
window.__synthOSPageDirty = false;
|
|
342
|
+
window.location.href = result.data.redirect;
|
|
343
|
+
} else {
|
|
344
|
+
closeSaveModal();
|
|
345
|
+
showError(result.data.error || 'An unknown error occurred');
|
|
346
|
+
confirmBtn.disabled = false;
|
|
347
|
+
confirmBtn.textContent = 'Save';
|
|
348
|
+
}
|
|
349
|
+
})
|
|
350
|
+
.catch(function(err) {
|
|
351
|
+
closeSaveModal();
|
|
352
|
+
showError('Network error: ' + err.message);
|
|
353
|
+
confirmBtn.disabled = false;
|
|
354
|
+
confirmBtn.textContent = 'Save';
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Expose openSaveModal globally for toolbar button
|
|
359
|
+
window.__synthOSOpenSaveModal = openSaveModal;
|
|
360
|
+
|
|
361
|
+
// --- Event listeners ---
|
|
362
|
+
document.getElementById('synthos-saveCloseBtn').addEventListener('click', closeSaveModal);
|
|
363
|
+
document.getElementById('synthos-saveCancelBtn').addEventListener('click', closeSaveModal);
|
|
364
|
+
document.getElementById('synthos-saveConfirmBtn').addEventListener('click', submitSave);
|
|
365
|
+
document.getElementById('synthos-errorCloseBtn').addEventListener('click', closeError);
|
|
366
|
+
document.getElementById('synthos-errorOkBtn').addEventListener('click', closeError);
|
|
367
|
+
|
|
368
|
+
var saveModalMouseDownTarget = null;
|
|
369
|
+
modal.addEventListener('mousedown', function(e) { saveModalMouseDownTarget = e.target; });
|
|
370
|
+
modal.addEventListener('click', function(e) {
|
|
371
|
+
if (e.target === modal && saveModalMouseDownTarget === modal) closeSaveModal();
|
|
372
|
+
saveModalMouseDownTarget = null;
|
|
373
|
+
});
|
|
374
|
+
var errorModalMouseDownTarget = null;
|
|
375
|
+
errorModal.addEventListener('mousedown', function(e) { errorModalMouseDownTarget = e.target; });
|
|
376
|
+
errorModal.addEventListener('click', function(e) {
|
|
377
|
+
if (e.target === errorModal && errorModalMouseDownTarget === errorModal) closeError();
|
|
378
|
+
errorModalMouseDownTarget = null;
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
document.addEventListener('keydown', function(e) {
|
|
382
|
+
if (e.key === 'Escape') {
|
|
383
|
+
if (modal.classList.contains('show')) closeSaveModal();
|
|
384
|
+
if (errorModal.classList.contains('show')) closeError();
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Enter key in title/categories inputs triggers save
|
|
389
|
+
titleInput.addEventListener('keydown', function(e) {
|
|
390
|
+
if (e.key === 'Enter') { e.preventDefault(); submitSave(); }
|
|
391
|
+
});
|
|
392
|
+
categoriesInput.addEventListener('keydown', function(e) {
|
|
393
|
+
if (e.key === 'Enter') { e.preventDefault(); submitSave(); }
|
|
394
|
+
});
|
|
395
|
+
})();
|
|
396
|
+
|
|
397
|
+
// 4. Chat scroll to bottom
|
|
398
|
+
var chatMessages = document.getElementById('chatMessages');
|
|
399
|
+
if (chatMessages) {
|
|
400
|
+
chatMessages.scrollTo({
|
|
401
|
+
top: chatMessages.scrollHeight,
|
|
402
|
+
behavior: 'smooth'
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// 5. Shell toolbar button wiring
|
|
407
|
+
(function() {
|
|
408
|
+
var DB_NAME = 'synthos-ui';
|
|
409
|
+
var STORE_NAME = 'panel-state';
|
|
410
|
+
var pageName = window.location.pathname.replace(/^\//, '') || 'builder';
|
|
411
|
+
var isBuilderPage = pageName === 'builder';
|
|
412
|
+
|
|
413
|
+
// IndexedDB helpers
|
|
414
|
+
function openDB(cb) {
|
|
415
|
+
var req = indexedDB.open(DB_NAME, 1);
|
|
416
|
+
req.onupgradeneeded = function() {
|
|
417
|
+
var db = req.result;
|
|
418
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
419
|
+
db.createObjectStore(STORE_NAME);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
req.onsuccess = function() { cb(req.result); };
|
|
423
|
+
req.onerror = function() { cb(null); };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function saveCollapsed(collapsed) {
|
|
427
|
+
openDB(function(db) {
|
|
428
|
+
if (!db) return;
|
|
429
|
+
var tx = db.transaction(STORE_NAME, 'readwrite');
|
|
430
|
+
tx.objectStore(STORE_NAME).put(collapsed, pageName);
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function loadCollapsed(cb) {
|
|
435
|
+
openDB(function(db) {
|
|
436
|
+
if (!db) { cb(null); return; }
|
|
437
|
+
var tx = db.transaction(STORE_NAME, 'readonly');
|
|
438
|
+
var req = tx.objectStore(STORE_NAME).get(pageName);
|
|
439
|
+
req.onsuccess = function() { cb(req.result); };
|
|
440
|
+
req.onerror = function() { cb(null); };
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function setCollapsed(collapsed) {
|
|
445
|
+
if (collapsed) {
|
|
446
|
+
document.body.classList.add('chat-collapsed');
|
|
447
|
+
} else {
|
|
448
|
+
document.body.classList.remove('chat-collapsed');
|
|
449
|
+
}
|
|
450
|
+
saveCollapsed(collapsed);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Restore state — builder page always starts open
|
|
454
|
+
if (isBuilderPage) {
|
|
455
|
+
document.body.classList.remove('chat-collapsed');
|
|
456
|
+
} else {
|
|
457
|
+
loadCollapsed(function(val) {
|
|
458
|
+
if (val === true) {
|
|
459
|
+
document.body.classList.add('chat-collapsed');
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Builder toggle — show/hide chat panel
|
|
465
|
+
var builderToggle = document.getElementById('builderToggle');
|
|
466
|
+
if (builderToggle) {
|
|
467
|
+
builderToggle.addEventListener('click', function() {
|
|
468
|
+
var collapsed = !document.body.classList.contains('chat-collapsed');
|
|
469
|
+
setCollapsed(collapsed);
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Builder close button — same as collapse
|
|
474
|
+
var builderClose = document.getElementById('builderClose');
|
|
475
|
+
if (builderClose) {
|
|
476
|
+
builderClose.addEventListener('click', function() {
|
|
477
|
+
setCollapsed(true);
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Pages button — navigate to pages gallery
|
|
482
|
+
var pagesBtn = document.getElementById('pagesBtn');
|
|
483
|
+
if (pagesBtn) {
|
|
484
|
+
pagesBtn.addEventListener('click', function() {
|
|
485
|
+
window.__synthOSNavigateTo('/pages');
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Save button — open save modal
|
|
490
|
+
var saveBtn = document.getElementById('saveBtn');
|
|
491
|
+
if (saveBtn) {
|
|
492
|
+
saveBtn.addEventListener('click', function() {
|
|
493
|
+
if (window.__synthOSOpenSaveModal) window.__synthOSOpenSaveModal();
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Settings button — navigate to settings page
|
|
498
|
+
var settingsBtn = document.getElementById('settingsBtn');
|
|
499
|
+
if (settingsBtn) {
|
|
500
|
+
settingsBtn.addEventListener('click', function() {
|
|
501
|
+
window.__synthOSNavigateTo('/settings');
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
})();
|
|
505
|
+
|
|
506
|
+
// 6. Focus management — prevent viewer content from stealing keystrokes
|
|
507
|
+
(function() {
|
|
508
|
+
var ci = document.getElementById('chatInput');
|
|
509
|
+
var vp = document.getElementById('viewerPanel');
|
|
510
|
+
if (!ci || !vp) return;
|
|
511
|
+
|
|
512
|
+
ci.addEventListener('mousedown', function(e) {
|
|
513
|
+
e.stopPropagation();
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
['keydown', 'keyup', 'keypress'].forEach(function(type) {
|
|
517
|
+
document.addEventListener(type, function(e) {
|
|
518
|
+
if (document.activeElement === ci) {
|
|
519
|
+
// Allow Enter (without Shift) to reach the submit handler
|
|
520
|
+
if (e.key === 'Enter' && !e.shiftKey) return;
|
|
521
|
+
e.stopImmediatePropagation();
|
|
522
|
+
}
|
|
523
|
+
}, true);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
vp.setAttribute('tabindex', '-1');
|
|
527
|
+
ci.addEventListener('blur', function() {
|
|
528
|
+
vp.focus();
|
|
529
|
+
});
|
|
530
|
+
})();
|
|
531
|
+
|
|
532
|
+
// 7. Brainstorm — dynamic brainstorming UI (available on every v2 page)
|
|
533
|
+
(function() {
|
|
534
|
+
var chatInput = document.getElementById('chatInput');
|
|
535
|
+
if (!chatInput) return;
|
|
536
|
+
|
|
537
|
+
// --- Create icon row (.chat-input-wrapper) appended to #chatForm ---
|
|
538
|
+
var form = document.getElementById('chatForm');
|
|
539
|
+
var wrapper = document.querySelector('.chat-input-wrapper');
|
|
540
|
+
if (!wrapper) {
|
|
541
|
+
wrapper = document.createElement('div');
|
|
542
|
+
wrapper.className = 'chat-input-wrapper';
|
|
543
|
+
if (form) form.appendChild(wrapper);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// --- Create brainstorm icon button ---
|
|
547
|
+
var brainstormBtn = document.createElement('button');
|
|
548
|
+
brainstormBtn.type = 'button';
|
|
549
|
+
brainstormBtn.className = 'brainstorm-icon-btn';
|
|
550
|
+
if (window.__synthOSTooltip) window.__synthOSTooltip(brainstormBtn, 'Brainstorm ideas');
|
|
551
|
+
brainstormBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
|
|
552
|
+
'<circle cx="12" cy="12" r="3"></circle>' +
|
|
553
|
+
'<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"></path>' +
|
|
554
|
+
'</svg>';
|
|
555
|
+
wrapper.appendChild(brainstormBtn);
|
|
556
|
+
|
|
557
|
+
// --- Create send button ---
|
|
558
|
+
var sendBtn = document.createElement('button');
|
|
559
|
+
sendBtn.type = 'submit';
|
|
560
|
+
sendBtn.className = 'chat-send-btn';
|
|
561
|
+
if (window.__synthOSTooltip) window.__synthOSTooltip(sendBtn, 'Send message');
|
|
562
|
+
sendBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
|
|
563
|
+
'<line x1="12" y1="19" x2="12" y2="5"></line>' +
|
|
564
|
+
'<polyline points="5 12 12 5 19 12"></polyline>' +
|
|
565
|
+
'</svg>';
|
|
566
|
+
wrapper.appendChild(sendBtn);
|
|
567
|
+
|
|
568
|
+
// --- Auto-grow textarea ---
|
|
569
|
+
chatInput.addEventListener('input', function() {
|
|
570
|
+
this.style.height = 'auto';
|
|
571
|
+
this.style.height = this.scrollHeight + 'px';
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// --- Create brainstorm modal ---
|
|
575
|
+
var modal = document.createElement('div');
|
|
576
|
+
modal.id = 'synthos-brainstormModal';
|
|
577
|
+
modal.className = 'modal-overlay brainstorm-modal';
|
|
578
|
+
modal.innerHTML =
|
|
579
|
+
'<div class="modal-content">' +
|
|
580
|
+
'<div class="modal-header">' +
|
|
581
|
+
'<span>Brainstorm</span>' +
|
|
582
|
+
'<button type="button" class="brainstorm-close-btn" id="synthos-brainstormCloseBtn">×</button>' +
|
|
583
|
+
'</div>' +
|
|
584
|
+
'<div class="brainstorm-messages" id="synthos-brainstormMessages"></div>' +
|
|
585
|
+
'<div class="brainstorm-input-row">' +
|
|
586
|
+
'<input type="text" class="brainstorm-input" id="synthos-brainstormInput" placeholder="What\'s on your mind...">' +
|
|
587
|
+
'<button type="button" class="brainstorm-send-btn" id="synthos-brainstormSendBtn">Send</button>' +
|
|
588
|
+
'</div>' +
|
|
589
|
+
'</div>';
|
|
590
|
+
document.body.appendChild(modal);
|
|
591
|
+
|
|
592
|
+
// --- State ---
|
|
593
|
+
var brainstormHistory = [];
|
|
594
|
+
|
|
595
|
+
// --- Helpers ---
|
|
596
|
+
function openBrainstorm() {
|
|
597
|
+
modal.classList.add('show');
|
|
598
|
+
// Grab text from chat input as initial topic
|
|
599
|
+
var topic = chatInput.value.trim();
|
|
600
|
+
if (topic) {
|
|
601
|
+
chatInput.value = '';
|
|
602
|
+
sendBrainstormText(topic, true);
|
|
603
|
+
} else {
|
|
604
|
+
// No topic — send context-only opener so LLM starts the brainstorm
|
|
605
|
+
sendBrainstormText('', true);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function closeBrainstorm() {
|
|
610
|
+
modal.classList.remove('show');
|
|
611
|
+
brainstormHistory = [];
|
|
612
|
+
document.getElementById('synthos-brainstormMessages').innerHTML = '';
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function scrollBrainstormToBottom() {
|
|
616
|
+
var el = document.getElementById('synthos-brainstormMessages');
|
|
617
|
+
el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' });
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function escapeHtml(str) {
|
|
621
|
+
var div = document.createElement('div');
|
|
622
|
+
div.appendChild(document.createTextNode(str));
|
|
623
|
+
return div.innerHTML;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function appendBrainstormMessage(role, text, prompt, suggestions, isOpener) {
|
|
627
|
+
var div = document.createElement('div');
|
|
628
|
+
div.className = 'brainstorm-message ' + (role === 'user' ? 'brainstorm-user' : 'brainstorm-assistant');
|
|
629
|
+
if (role === 'assistant') {
|
|
630
|
+
var html;
|
|
631
|
+
if (typeof marked !== 'undefined') {
|
|
632
|
+
html = marked.parse(text);
|
|
633
|
+
} else {
|
|
634
|
+
html = escapeHtml(text);
|
|
635
|
+
}
|
|
636
|
+
div.innerHTML = '<strong>' + pn + ':</strong> ' + html;
|
|
637
|
+
// Clickable suggestion chips
|
|
638
|
+
if (suggestions && suggestions.length > 0) {
|
|
639
|
+
var chips = document.createElement('div');
|
|
640
|
+
chips.className = 'brainstorm-suggestions';
|
|
641
|
+
suggestions.forEach(function(s) {
|
|
642
|
+
var chip = document.createElement('button');
|
|
643
|
+
chip.type = 'button';
|
|
644
|
+
chip.className = 'brainstorm-suggestion-chip';
|
|
645
|
+
chip.textContent = s;
|
|
646
|
+
chip.addEventListener('click', function() {
|
|
647
|
+
submitSuggestion(s);
|
|
648
|
+
});
|
|
649
|
+
chips.appendChild(chip);
|
|
650
|
+
});
|
|
651
|
+
div.appendChild(chips);
|
|
652
|
+
}
|
|
653
|
+
// "Build It" button — skip on the opener response
|
|
654
|
+
if (prompt && !isOpener) {
|
|
655
|
+
var btnRow = document.createElement('div');
|
|
656
|
+
btnRow.className = 'brainstorm-build-row';
|
|
657
|
+
var buildBtn = document.createElement('button');
|
|
658
|
+
buildBtn.type = 'button';
|
|
659
|
+
buildBtn.className = 'brainstorm-build-btn';
|
|
660
|
+
buildBtn.textContent = 'Build It';
|
|
661
|
+
buildBtn.setAttribute('data-prompt', prompt);
|
|
662
|
+
buildBtn.addEventListener('click', function() {
|
|
663
|
+
chatInput.value = this.getAttribute('data-prompt');
|
|
664
|
+
closeBrainstorm();
|
|
665
|
+
chatInput.focus();
|
|
666
|
+
});
|
|
667
|
+
btnRow.appendChild(buildBtn);
|
|
668
|
+
div.appendChild(btnRow);
|
|
669
|
+
}
|
|
670
|
+
} else {
|
|
671
|
+
div.textContent = text;
|
|
672
|
+
}
|
|
673
|
+
document.getElementById('synthos-brainstormMessages').appendChild(div);
|
|
674
|
+
scrollBrainstormToBottom();
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function submitSuggestion(text) {
|
|
678
|
+
// Disable old suggestion chips so they can't be double-clicked
|
|
679
|
+
var oldChips = document.querySelectorAll('#synthos-brainstormMessages .brainstorm-suggestion-chip');
|
|
680
|
+
for (var i = 0; i < oldChips.length; i++) {
|
|
681
|
+
oldChips[i].disabled = true;
|
|
682
|
+
}
|
|
683
|
+
sendBrainstormText(text, false);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function getBrainstormContext() {
|
|
687
|
+
var chatEl = document.getElementById('chatMessages');
|
|
688
|
+
if (!chatEl) return '<CHAT_HISTORY>\n';
|
|
689
|
+
var msgs = chatEl.querySelectorAll('.chat-message');
|
|
690
|
+
var lines = [];
|
|
691
|
+
var started = false;
|
|
692
|
+
for (var i = 0; i < msgs.length; i++) {
|
|
693
|
+
var text = msgs[i].innerText;
|
|
694
|
+
if (!started && /^User:/i.test(text.trim())) started = true;
|
|
695
|
+
if (started) lines.push(text);
|
|
696
|
+
}
|
|
697
|
+
return '<CHAT_HISTORY>\n' + lines.join('\n');
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Send from the input field
|
|
701
|
+
function sendBrainstormMessage() {
|
|
702
|
+
var input = document.getElementById('synthos-brainstormInput');
|
|
703
|
+
var text = input.value.trim();
|
|
704
|
+
if (!text) return;
|
|
705
|
+
input.value = '';
|
|
706
|
+
sendBrainstormText(text, false);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Core fetch — isOpener=true means this is the initial call when brainstorm opens
|
|
710
|
+
function sendBrainstormText(text, isOpener) {
|
|
711
|
+
var input = document.getElementById('synthos-brainstormInput');
|
|
712
|
+
var userMsg = text || (isOpener ? 'Look at the conversation so far and suggest what we could build or improve.' : '');
|
|
713
|
+
if (!userMsg) return;
|
|
714
|
+
|
|
715
|
+
// Show user message in chat (skip for auto-generated opener)
|
|
716
|
+
if (text) {
|
|
717
|
+
appendBrainstormMessage('user', text);
|
|
718
|
+
}
|
|
719
|
+
brainstormHistory.push({ role: 'user', content: userMsg });
|
|
720
|
+
|
|
721
|
+
var thinking = document.createElement('div');
|
|
722
|
+
thinking.className = 'brainstorm-thinking';
|
|
723
|
+
thinking.id = 'synthos-brainstormThinking';
|
|
724
|
+
thinking.textContent = 'Thinking...';
|
|
725
|
+
document.getElementById('synthos-brainstormMessages').appendChild(thinking);
|
|
726
|
+
scrollBrainstormToBottom();
|
|
727
|
+
|
|
728
|
+
input.disabled = true;
|
|
729
|
+
document.getElementById('synthos-brainstormSendBtn').disabled = true;
|
|
730
|
+
|
|
731
|
+
fetch('/api/brainstorm', {
|
|
732
|
+
method: 'POST',
|
|
733
|
+
headers: { 'Content-Type': 'application/json' },
|
|
734
|
+
body: JSON.stringify({
|
|
735
|
+
context: getBrainstormContext(),
|
|
736
|
+
messages: brainstormHistory
|
|
737
|
+
})
|
|
738
|
+
})
|
|
739
|
+
.then(function(res) {
|
|
740
|
+
if (!res.ok) throw new Error('Brainstorm request failed');
|
|
741
|
+
return res.json();
|
|
742
|
+
})
|
|
743
|
+
.then(function(data) {
|
|
744
|
+
var thinkingEl = document.getElementById('synthos-brainstormThinking');
|
|
745
|
+
if (thinkingEl) thinkingEl.remove();
|
|
746
|
+
|
|
747
|
+
var response = data.response || 'Sorry, I didn\'t get a response.';
|
|
748
|
+
var prompt = data.prompt || '';
|
|
749
|
+
var suggestions = Array.isArray(data.suggestions) ? data.suggestions : [];
|
|
750
|
+
appendBrainstormMessage('assistant', response, prompt, suggestions, isOpener);
|
|
751
|
+
brainstormHistory.push({
|
|
752
|
+
role: 'assistant',
|
|
753
|
+
content: response + '\n\n[Suggested prompt: ' + prompt + ']'
|
|
754
|
+
});
|
|
755
|
+
})
|
|
756
|
+
.catch(function(err) {
|
|
757
|
+
var thinkingEl = document.getElementById('synthos-brainstormThinking');
|
|
758
|
+
if (thinkingEl) thinkingEl.remove();
|
|
759
|
+
appendBrainstormMessage('assistant', 'Something went wrong: ' + err.message);
|
|
760
|
+
})
|
|
761
|
+
.finally(function() {
|
|
762
|
+
input.disabled = false;
|
|
763
|
+
document.getElementById('synthos-brainstormSendBtn').disabled = false;
|
|
764
|
+
input.focus();
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// --- Event listeners ---
|
|
769
|
+
brainstormBtn.addEventListener('click', openBrainstorm);
|
|
770
|
+
document.getElementById('synthos-brainstormCloseBtn').addEventListener('click', closeBrainstorm);
|
|
771
|
+
|
|
772
|
+
var brainstormMouseDownTarget = null;
|
|
773
|
+
modal.addEventListener('mousedown', function(e) { brainstormMouseDownTarget = e.target; });
|
|
774
|
+
modal.addEventListener('click', function(e) {
|
|
775
|
+
if (e.target === modal && brainstormMouseDownTarget === modal) closeBrainstorm();
|
|
776
|
+
brainstormMouseDownTarget = null;
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
document.addEventListener('keydown', function(e) {
|
|
780
|
+
if (e.key === 'Escape' && modal.classList.contains('show')) closeBrainstorm();
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
document.getElementById('synthos-brainstormSendBtn').addEventListener('click', sendBrainstormMessage);
|
|
784
|
+
document.getElementById('synthos-brainstormInput').addEventListener('keydown', function(e) {
|
|
785
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
786
|
+
e.preventDefault();
|
|
787
|
+
sendBrainstormMessage();
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
})();
|
|
791
|
+
|
|
792
|
+
// 8. Attach button — + menu, attachment pills, file picker, screenshot tool
|
|
793
|
+
(function() {
|
|
794
|
+
var chatInput = document.getElementById('chatInput');
|
|
795
|
+
if (!chatInput) return;
|
|
796
|
+
var chatForm = document.getElementById('chatForm');
|
|
797
|
+
var wrapper = chatForm ? chatForm.querySelector('.chat-input-wrapper') : null;
|
|
798
|
+
if (!wrapper) return;
|
|
799
|
+
|
|
800
|
+
// --- Attachments state ---
|
|
801
|
+
if (!window.__synthOSAttachments) window.__synthOSAttachments = [];
|
|
802
|
+
|
|
803
|
+
// --- Create pills container (inside #chatForm, before textarea) ---
|
|
804
|
+
var pillsContainer = document.createElement('div');
|
|
805
|
+
pillsContainer.className = 'attachment-pills';
|
|
806
|
+
if (chatForm) {
|
|
807
|
+
chatForm.insertBefore(pillsContainer, chatForm.firstChild);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function renderPills() {
|
|
811
|
+
pillsContainer.innerHTML = '';
|
|
812
|
+
var attachments = window.__synthOSAttachments;
|
|
813
|
+
if (!attachments || attachments.length === 0) return;
|
|
814
|
+
for (var i = 0; i < attachments.length; i++) {
|
|
815
|
+
(function(idx) {
|
|
816
|
+
var att = attachments[idx];
|
|
817
|
+
var pill = document.createElement('div');
|
|
818
|
+
pill.className = 'attachment-pill';
|
|
819
|
+
|
|
820
|
+
var thumb = document.createElement('img');
|
|
821
|
+
thumb.src = 'data:' + att.mediaType + ';base64,' + att.data;
|
|
822
|
+
thumb.style.cssText = 'width:24px;height:24px;object-fit:cover;border-radius:3px;';
|
|
823
|
+
pill.appendChild(thumb);
|
|
824
|
+
|
|
825
|
+
var nameSpan = document.createElement('span');
|
|
826
|
+
nameSpan.textContent = att.name || 'image';
|
|
827
|
+
nameSpan.style.cssText = 'flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;';
|
|
828
|
+
pill.appendChild(nameSpan);
|
|
829
|
+
|
|
830
|
+
var removeBtn = document.createElement('button');
|
|
831
|
+
removeBtn.type = 'button';
|
|
832
|
+
removeBtn.className = 'attachment-pill-remove';
|
|
833
|
+
removeBtn.textContent = '\u00d7';
|
|
834
|
+
removeBtn.addEventListener('click', function() {
|
|
835
|
+
window.__synthOSAttachments.splice(idx, 1);
|
|
836
|
+
renderPills();
|
|
837
|
+
});
|
|
838
|
+
pill.appendChild(removeBtn);
|
|
839
|
+
|
|
840
|
+
pillsContainer.appendChild(pill);
|
|
841
|
+
})(i);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Expose for debugging
|
|
846
|
+
window.__synthOSRenderPills = renderPills;
|
|
847
|
+
|
|
848
|
+
// --- Helper: add an image attachment from a data URL ---
|
|
849
|
+
function addImageFromDataUrl(dataUrl, name) {
|
|
850
|
+
var commaIdx = dataUrl.indexOf(',');
|
|
851
|
+
if (commaIdx === -1) return;
|
|
852
|
+
var meta = dataUrl.substring(0, commaIdx); // data:image/png;base64
|
|
853
|
+
var base64 = dataUrl.substring(commaIdx + 1);
|
|
854
|
+
var mediaType = meta.replace('data:', '').replace(';base64', '');
|
|
855
|
+
window.__synthOSAttachments.push({
|
|
856
|
+
mediaType: mediaType,
|
|
857
|
+
data: base64,
|
|
858
|
+
name: name || 'image'
|
|
859
|
+
});
|
|
860
|
+
renderPills();
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// --- Hidden file input ---
|
|
864
|
+
var fileInput = document.createElement('input');
|
|
865
|
+
fileInput.type = 'file';
|
|
866
|
+
fileInput.accept = 'image/*';
|
|
867
|
+
fileInput.style.display = 'none';
|
|
868
|
+
document.body.appendChild(fileInput);
|
|
869
|
+
|
|
870
|
+
fileInput.addEventListener('change', function() {
|
|
871
|
+
var file = fileInput.files && fileInput.files[0];
|
|
872
|
+
if (!file) return;
|
|
873
|
+
var reader = new FileReader();
|
|
874
|
+
reader.onload = function() {
|
|
875
|
+
addImageFromDataUrl(reader.result, file.name);
|
|
876
|
+
};
|
|
877
|
+
reader.readAsDataURL(file);
|
|
878
|
+
fileInput.value = '';
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
// --- Paste image from clipboard (document-level to catch all pastes) ---
|
|
882
|
+
document.addEventListener('paste', function(e) {
|
|
883
|
+
// Only handle pastes when chat input is focused or no other editable is focused
|
|
884
|
+
var active = document.activeElement;
|
|
885
|
+
var isEditable = active && (active.isContentEditable ||
|
|
886
|
+
(active.tagName === 'TEXTAREA' && active !== chatInput) ||
|
|
887
|
+
(active.tagName === 'INPUT' && active !== chatInput));
|
|
888
|
+
if (isEditable) return;
|
|
889
|
+
|
|
890
|
+
var items = e.clipboardData && e.clipboardData.items;
|
|
891
|
+
if (!items) return;
|
|
892
|
+
for (var i = 0; i < items.length; i++) {
|
|
893
|
+
if (items[i].type.indexOf('image/') === 0) {
|
|
894
|
+
var blob = items[i].getAsFile();
|
|
895
|
+
if (!blob) continue;
|
|
896
|
+
e.preventDefault();
|
|
897
|
+
var reader = new FileReader();
|
|
898
|
+
reader.onload = function() {
|
|
899
|
+
addImageFromDataUrl(reader.result, 'pasted-image.png');
|
|
900
|
+
};
|
|
901
|
+
reader.readAsDataURL(blob);
|
|
902
|
+
return; // only handle the first image
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
// --- + button ---
|
|
908
|
+
var attachBtn = document.createElement('button');
|
|
909
|
+
attachBtn.type = 'button';
|
|
910
|
+
attachBtn.className = 'attach-btn';
|
|
911
|
+
if (window.__synthOSTooltip) window.__synthOSTooltip(attachBtn, 'Attach file or screenshot');
|
|
912
|
+
attachBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>';
|
|
913
|
+
wrapper.insertBefore(attachBtn, wrapper.firstChild);
|
|
914
|
+
|
|
915
|
+
// --- Popup menu ---
|
|
916
|
+
var menu = document.createElement('div');
|
|
917
|
+
menu.className = 'attach-menu';
|
|
918
|
+
|
|
919
|
+
var menuAttachFile = document.createElement('div');
|
|
920
|
+
menuAttachFile.className = 'attach-menu-item';
|
|
921
|
+
menuAttachFile.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48"></path></svg> Attach File';
|
|
922
|
+
menu.appendChild(menuAttachFile);
|
|
923
|
+
|
|
924
|
+
var menuScreenshot = document.createElement('div');
|
|
925
|
+
menuScreenshot.className = 'attach-menu-item';
|
|
926
|
+
menuScreenshot.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg> Screenshot';
|
|
927
|
+
menu.appendChild(menuScreenshot);
|
|
928
|
+
|
|
929
|
+
wrapper.appendChild(menu);
|
|
930
|
+
|
|
931
|
+
var menuOpen = false;
|
|
932
|
+
function toggleMenu() {
|
|
933
|
+
menuOpen = !menuOpen;
|
|
934
|
+
menu.style.display = menuOpen ? 'flex' : 'none';
|
|
935
|
+
}
|
|
936
|
+
function closeMenu() {
|
|
937
|
+
menuOpen = false;
|
|
938
|
+
menu.style.display = 'none';
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
attachBtn.addEventListener('click', function(e) {
|
|
942
|
+
e.stopPropagation();
|
|
943
|
+
toggleMenu();
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
document.addEventListener('click', function() {
|
|
947
|
+
if (menuOpen) closeMenu();
|
|
948
|
+
});
|
|
949
|
+
menu.addEventListener('click', function(e) { e.stopPropagation(); });
|
|
950
|
+
|
|
951
|
+
menuAttachFile.addEventListener('click', function() {
|
|
952
|
+
closeMenu();
|
|
953
|
+
fileInput.click();
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
// --- Screenshot annotation flow (multi-rectangle) ---
|
|
957
|
+
menuScreenshot.addEventListener('click', function() {
|
|
958
|
+
closeMenu();
|
|
959
|
+
startScreenshotAnnotation();
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
function startScreenshotAnnotation() {
|
|
963
|
+
var viewerPanel = document.getElementById('viewerPanel');
|
|
964
|
+
if (!viewerPanel) return;
|
|
965
|
+
|
|
966
|
+
// Create overlay
|
|
967
|
+
var overlay = document.createElement('div');
|
|
968
|
+
overlay.className = 'screenshot-overlay';
|
|
969
|
+
|
|
970
|
+
// Instructions bar
|
|
971
|
+
var instrBar = document.createElement('div');
|
|
972
|
+
instrBar.style.cssText = 'position:absolute;top:10px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.7);color:white;padding:6px 14px;border-radius:6px;font-size:13px;z-index:10;pointer-events:none;';
|
|
973
|
+
instrBar.textContent = 'Draw rectangles to highlight areas, then click Capture';
|
|
974
|
+
overlay.appendChild(instrBar);
|
|
975
|
+
|
|
976
|
+
// Persistent action buttons (always visible)
|
|
977
|
+
var actions = document.createElement('div');
|
|
978
|
+
actions.className = 'screenshot-actions';
|
|
979
|
+
actions.style.cssText = 'position:absolute;bottom:16px;left:50%;transform:translateX(-50%);display:flex;gap:8px;z-index:10;';
|
|
980
|
+
|
|
981
|
+
var captureBtn = document.createElement('button');
|
|
982
|
+
captureBtn.type = 'button';
|
|
983
|
+
captureBtn.textContent = 'Capture';
|
|
984
|
+
captureBtn.className = 'brainstorm-send-btn';
|
|
985
|
+
captureBtn.style.cssText = 'padding:6px 16px;font-size:13px;';
|
|
986
|
+
|
|
987
|
+
var cancelBtn = document.createElement('button');
|
|
988
|
+
cancelBtn.type = 'button';
|
|
989
|
+
cancelBtn.textContent = 'Cancel';
|
|
990
|
+
cancelBtn.className = 'brainstorm-send-btn';
|
|
991
|
+
cancelBtn.style.cssText = 'padding:6px 16px;font-size:13px;background:transparent;border:1px solid rgba(255,255,255,0.3);color:white;';
|
|
992
|
+
|
|
993
|
+
actions.appendChild(captureBtn);
|
|
994
|
+
actions.appendChild(cancelBtn);
|
|
995
|
+
overlay.appendChild(actions);
|
|
996
|
+
|
|
997
|
+
// Drawing state
|
|
998
|
+
var currentRect = null;
|
|
999
|
+
var startX, startY, isDrawing = false;
|
|
1000
|
+
var allRects = []; // Array of {el, x, y, w, h}
|
|
1001
|
+
|
|
1002
|
+
overlay.addEventListener('mousedown', function(e) {
|
|
1003
|
+
if (e.target.tagName === 'BUTTON') return;
|
|
1004
|
+
isDrawing = true;
|
|
1005
|
+
var overlayBounds = overlay.getBoundingClientRect();
|
|
1006
|
+
startX = e.clientX - overlayBounds.left;
|
|
1007
|
+
startY = e.clientY - overlayBounds.top;
|
|
1008
|
+
|
|
1009
|
+
// Create a new rectangle element
|
|
1010
|
+
currentRect = document.createElement('div');
|
|
1011
|
+
currentRect.className = 'screenshot-rect';
|
|
1012
|
+
currentRect.style.display = 'block';
|
|
1013
|
+
currentRect.style.left = startX + 'px';
|
|
1014
|
+
currentRect.style.top = startY + 'px';
|
|
1015
|
+
currentRect.style.width = '0';
|
|
1016
|
+
currentRect.style.height = '0';
|
|
1017
|
+
overlay.appendChild(currentRect);
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
overlay.addEventListener('mousemove', function(e) {
|
|
1021
|
+
if (!isDrawing || !currentRect) return;
|
|
1022
|
+
var overlayBounds = overlay.getBoundingClientRect();
|
|
1023
|
+
var curX = e.clientX - overlayBounds.left;
|
|
1024
|
+
var curY = e.clientY - overlayBounds.top;
|
|
1025
|
+
var x = Math.min(startX, curX);
|
|
1026
|
+
var y = Math.min(startY, curY);
|
|
1027
|
+
var w = Math.abs(curX - startX);
|
|
1028
|
+
var h = Math.abs(curY - startY);
|
|
1029
|
+
currentRect.style.left = x + 'px';
|
|
1030
|
+
currentRect.style.top = y + 'px';
|
|
1031
|
+
currentRect.style.width = w + 'px';
|
|
1032
|
+
currentRect.style.height = h + 'px';
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
overlay.addEventListener('mouseup', function() {
|
|
1036
|
+
if (!isDrawing || !currentRect) return;
|
|
1037
|
+
isDrawing = false;
|
|
1038
|
+
var w = parseInt(currentRect.style.width);
|
|
1039
|
+
var h = parseInt(currentRect.style.height);
|
|
1040
|
+
if (w < 10 || h < 10) {
|
|
1041
|
+
// Too small — discard
|
|
1042
|
+
currentRect.remove();
|
|
1043
|
+
currentRect = null;
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
allRects.push({
|
|
1047
|
+
el: currentRect,
|
|
1048
|
+
x: parseInt(currentRect.style.left),
|
|
1049
|
+
y: parseInt(currentRect.style.top),
|
|
1050
|
+
w: w,
|
|
1051
|
+
h: h
|
|
1052
|
+
});
|
|
1053
|
+
currentRect = null;
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
captureBtn.addEventListener('click', function() {
|
|
1057
|
+
doCapture();
|
|
1058
|
+
});
|
|
1059
|
+
cancelBtn.addEventListener('click', function() {
|
|
1060
|
+
cleanup();
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
function doCapture() {
|
|
1064
|
+
if (typeof html2canvas === 'undefined') {
|
|
1065
|
+
console.error('html2canvas not loaded');
|
|
1066
|
+
cleanup();
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Capture the full viewer panel without the overlay
|
|
1071
|
+
overlay.style.visibility = 'hidden';
|
|
1072
|
+
|
|
1073
|
+
html2canvas(viewerPanel, { useCORS: true, logging: false }).then(function(fullCanvas) {
|
|
1074
|
+
// Draw red rectangles onto the captured canvas
|
|
1075
|
+
var vpRect = viewerPanel.getBoundingClientRect();
|
|
1076
|
+
var scaleX = fullCanvas.width / vpRect.width;
|
|
1077
|
+
var scaleY = fullCanvas.height / vpRect.height;
|
|
1078
|
+
|
|
1079
|
+
var ctx = fullCanvas.getContext('2d');
|
|
1080
|
+
ctx.strokeStyle = 'red';
|
|
1081
|
+
ctx.lineWidth = 3 * Math.max(scaleX, scaleY);
|
|
1082
|
+
|
|
1083
|
+
for (var i = 0; i < allRects.length; i++) {
|
|
1084
|
+
var r = allRects[i];
|
|
1085
|
+
ctx.strokeRect(
|
|
1086
|
+
Math.round(r.x * scaleX),
|
|
1087
|
+
Math.round(r.y * scaleY),
|
|
1088
|
+
Math.round(r.w * scaleX),
|
|
1089
|
+
Math.round(r.h * scaleY)
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
var dataUrl = fullCanvas.toDataURL('image/png');
|
|
1094
|
+
var base64 = dataUrl.split(',')[1];
|
|
1095
|
+
window.__synthOSAttachments.push({
|
|
1096
|
+
mediaType: 'image/png',
|
|
1097
|
+
data: base64,
|
|
1098
|
+
name: 'screenshot.png'
|
|
1099
|
+
});
|
|
1100
|
+
renderPills();
|
|
1101
|
+
cleanup();
|
|
1102
|
+
}).catch(function(err) {
|
|
1103
|
+
console.error('Screenshot capture failed:', err);
|
|
1104
|
+
cleanup();
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
function cleanup() {
|
|
1109
|
+
if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
|
|
1110
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// Escape to cancel
|
|
1114
|
+
function onKeyDown(e) {
|
|
1115
|
+
if (e.key === 'Escape') {
|
|
1116
|
+
cleanup();
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
document.addEventListener('keydown', onKeyDown);
|
|
1120
|
+
|
|
1121
|
+
viewerPanel.style.position = 'relative';
|
|
1122
|
+
viewerPanel.appendChild(overlay);
|
|
1123
|
+
}
|
|
1124
|
+
})();
|
|
1125
|
+
|
|
1126
|
+
// 9. Undo link — shown after the last chat message when a version exists
|
|
1127
|
+
(function() {
|
|
1128
|
+
var meta = document.querySelector('meta[name="synthos-version"]');
|
|
1129
|
+
if (!meta) return;
|
|
1130
|
+
var version = parseInt(meta.getAttribute('content'), 10);
|
|
1131
|
+
if (!version || version <= 0) return;
|
|
1132
|
+
|
|
1133
|
+
var cm = document.getElementById('chatMessages');
|
|
1134
|
+
if (!cm) return;
|
|
1135
|
+
|
|
1136
|
+
// Remove any existing undo links
|
|
1137
|
+
var existing = cm.querySelectorAll('.synthos-undo-link');
|
|
1138
|
+
for (var i = 0; i < existing.length; i++) existing[i].remove();
|
|
1139
|
+
|
|
1140
|
+
// Create undo link div
|
|
1141
|
+
var undoDiv = document.createElement('div');
|
|
1142
|
+
undoDiv.className = 'synthos-undo-link';
|
|
1143
|
+
undoDiv.style.cssText = 'text-align:right;padding:4px 12px 2px;';
|
|
1144
|
+
|
|
1145
|
+
var link = document.createElement('a');
|
|
1146
|
+
link.href = '#';
|
|
1147
|
+
link.textContent = 'undo';
|
|
1148
|
+
link.style.cssText = 'color:var(--accent-primary,#a78bfa);font-size:12px;text-decoration:none;opacity:0.7;';
|
|
1149
|
+
link.addEventListener('mouseenter', function() { link.style.opacity = '1'; link.style.textDecoration = 'underline'; });
|
|
1150
|
+
link.addEventListener('mouseleave', function() { link.style.opacity = '0.7'; link.style.textDecoration = 'none'; });
|
|
1151
|
+
|
|
1152
|
+
link.addEventListener('click', function(e) {
|
|
1153
|
+
e.preventDefault();
|
|
1154
|
+
var overlay = document.getElementById('loadingOverlay');
|
|
1155
|
+
if (overlay) overlay.style.display = 'flex';
|
|
1156
|
+
|
|
1157
|
+
fetch(window.location.pathname + '/undo', {
|
|
1158
|
+
method: 'POST',
|
|
1159
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1160
|
+
body: '{}'
|
|
1161
|
+
})
|
|
1162
|
+
.then(function(res) { return res.text(); })
|
|
1163
|
+
.then(function(html) {
|
|
1164
|
+
window.__synthOSChatPanel = false;
|
|
1165
|
+
window.__synthOSPageDirty = false;
|
|
1166
|
+
window.__synthOSNavigateTo = null;
|
|
1167
|
+
document.open();
|
|
1168
|
+
document.write(html);
|
|
1169
|
+
document.close();
|
|
1170
|
+
})
|
|
1171
|
+
.catch(function(err) {
|
|
1172
|
+
console.error('Undo failed:', err);
|
|
1173
|
+
if (overlay) overlay.style.display = 'none';
|
|
1174
|
+
});
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
undoDiv.appendChild(link);
|
|
1178
|
+
|
|
1179
|
+
// "try again" link — re-submits the last user message against the previous page state
|
|
1180
|
+
var msgs = cm.querySelectorAll('.chat-message');
|
|
1181
|
+
var lastUserMsg = '';
|
|
1182
|
+
for (var j = msgs.length - 1; j >= 0; j--) {
|
|
1183
|
+
var strong = msgs[j].querySelector('strong');
|
|
1184
|
+
if (strong && strong.textContent.trim().replace(/:$/, '') === 'User') {
|
|
1185
|
+
// Get the text content after "User:" — everything in the <p> minus the strong
|
|
1186
|
+
var p = msgs[j].querySelector('p');
|
|
1187
|
+
if (p) {
|
|
1188
|
+
var clone = p.cloneNode(true);
|
|
1189
|
+
var s = clone.querySelector('strong');
|
|
1190
|
+
if (s) s.remove();
|
|
1191
|
+
lastUserMsg = clone.textContent.trim();
|
|
1192
|
+
}
|
|
1193
|
+
break;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
if (lastUserMsg) {
|
|
1198
|
+
var sep = document.createTextNode(' · ');
|
|
1199
|
+
undoDiv.appendChild(sep);
|
|
1200
|
+
|
|
1201
|
+
var tryLink = document.createElement('a');
|
|
1202
|
+
tryLink.href = '#';
|
|
1203
|
+
tryLink.textContent = 'try again';
|
|
1204
|
+
tryLink.style.cssText = 'color:var(--accent-primary,#a78bfa);font-size:12px;text-decoration:none;opacity:0.7;';
|
|
1205
|
+
tryLink.addEventListener('mouseenter', function() { tryLink.style.opacity = '1'; tryLink.style.textDecoration = 'underline'; });
|
|
1206
|
+
tryLink.addEventListener('mouseleave', function() { tryLink.style.opacity = '0.7'; tryLink.style.textDecoration = 'none'; });
|
|
1207
|
+
|
|
1208
|
+
tryLink.addEventListener('click', function(e) {
|
|
1209
|
+
e.preventDefault();
|
|
1210
|
+
var overlay = document.getElementById('loadingOverlay');
|
|
1211
|
+
if (overlay) overlay.style.display = 'flex';
|
|
1212
|
+
var ci = document.getElementById('chatInput');
|
|
1213
|
+
if (ci) ci.disabled = true;
|
|
1214
|
+
var sb = document.querySelector('.chat-send-btn');
|
|
1215
|
+
if (sb) sb.disabled = true;
|
|
1216
|
+
|
|
1217
|
+
fetch(window.location.pathname, {
|
|
1218
|
+
method: 'POST',
|
|
1219
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1220
|
+
body: JSON.stringify({ message: lastUserMsg, tryAgain: true })
|
|
1221
|
+
})
|
|
1222
|
+
.then(function(res) { return res.text(); })
|
|
1223
|
+
.then(function(html) {
|
|
1224
|
+
window.__synthOSPageDirty = true;
|
|
1225
|
+
window.__synthOSChatPanel = false;
|
|
1226
|
+
window.__synthOSNavigateTo = null;
|
|
1227
|
+
document.open();
|
|
1228
|
+
document.write(html);
|
|
1229
|
+
document.close();
|
|
1230
|
+
})
|
|
1231
|
+
.catch(function(err) {
|
|
1232
|
+
console.error('Try again failed:', err);
|
|
1233
|
+
if (overlay) overlay.style.display = 'none';
|
|
1234
|
+
if (ci) ci.disabled = false;
|
|
1235
|
+
if (sb) sb.disabled = false;
|
|
1236
|
+
});
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
undoDiv.appendChild(tryLink);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
cm.appendChild(undoDiv);
|
|
1243
|
+
})();
|
|
1244
|
+
|
|
1245
|
+
// 10. Unsaved-changes prompt for required pages (FluentLM dialog)
|
|
1246
|
+
(function() {
|
|
1247
|
+
// Create the unsaved-changes confirmation dialog
|
|
1248
|
+
var overlay = document.createElement('div');
|
|
1249
|
+
overlay.className = 'flm-dialog-overlay';
|
|
1250
|
+
overlay.id = 'synthos-unsavedDialog';
|
|
1251
|
+
overlay.innerHTML =
|
|
1252
|
+
'<div class="flm-dialog">' +
|
|
1253
|
+
'<div class="flm-dialog-header">' +
|
|
1254
|
+
'<h2 class="flm-dialog-title">Unsaved Changes</h2>' +
|
|
1255
|
+
'<button class="flm-dialog-close" data-icon="Cancel" aria-label="Close" data-dialog-close></button>' +
|
|
1256
|
+
'</div>' +
|
|
1257
|
+
'<div class="flm-dialog-body">You have unsaved changes that will be lost if you leave this page.</div>' +
|
|
1258
|
+
'<div class="flm-dialog-footer">' +
|
|
1259
|
+
'<button class="flm-button" data-dialog-close id="synthos-unsavedStay">Stay</button>' +
|
|
1260
|
+
'<button class="flm-button flm-button--primary" id="synthos-unsavedLeave">Leave</button>' +
|
|
1261
|
+
'</div>' +
|
|
1262
|
+
'</div>';
|
|
1263
|
+
document.body.appendChild(overlay);
|
|
1264
|
+
|
|
1265
|
+
var pendingUrl = null;
|
|
1266
|
+
|
|
1267
|
+
document.getElementById('synthos-unsavedLeave').addEventListener('click', function() {
|
|
1268
|
+
overlay.classList.remove('is-open');
|
|
1269
|
+
if (pendingUrl) {
|
|
1270
|
+
window.__synthOSPageDirty = false;
|
|
1271
|
+
window.location.href = pendingUrl;
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
// Navigate with unsaved-changes guard
|
|
1276
|
+
window.__synthOSNavigateTo = function(url) {
|
|
1277
|
+
if (window.__synthOSPageDirty && window.pageInfo && window.pageInfo.isRequiredPage) {
|
|
1278
|
+
pendingUrl = url;
|
|
1279
|
+
overlay.classList.add('is-open');
|
|
1280
|
+
} else {
|
|
1281
|
+
window.location.href = url;
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
})();
|
|
1285
|
+
|
|
1286
|
+
// Initial focus — run after all setup (including chat-collapsed check)
|
|
1287
|
+
if (chatInput && !document.body.classList.contains('chat-collapsed')) {
|
|
1288
|
+
chatInput.focus();
|
|
1289
|
+
}
|
|
1290
|
+
})();
|