synthos 0.7.2 → 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 +215 -65
- 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/page.html +323 -0
- package/default-pages/oregon_trail/page.json +12 -0
- package/default-pages/retro_game_starter/page.html +1308 -0
- package/default-pages/retro_game_starter/page.json +12 -0
- package/default-pages/{sidebar_builder.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} +24 -29
- package/default-pages/{solar_explorer.json → solar_explorer/page.json} +4 -4
- 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_builder.html → two-panel_page/page.html} +13 -11
- package/default-pages/two-panel_page/page.json +10 -0
- package/default-pages/us_map/page.html +193 -0
- package/default-pages/us_map/page.json +12 -0
- package/default-pages/us_map_1850/page.html +326 -0
- package/default-pages/us_map_1850/page.json +12 -0
- package/default-pages/western_cities_1850/page.html +527 -0
- package/default-pages/western_cities_1850/page.json +12 -0
- 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.css → nebula-dawn.v2.css} +134 -0
- package/default-themes/nebula-dawn.v3.css +199 -0
- package/default-themes/{nebula-dusk.css → nebula-dusk.v2.css} +128 -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/a2a/a2aProvider.d.ts.map +1 -0
- package/dist/agents/a2a/a2aProvider.js +126 -0
- package/dist/agents/a2a/a2aProvider.js.map +1 -0
- package/dist/agents/discovery.d.ts.map +1 -0
- package/dist/agents/discovery.js +52 -0
- package/dist/agents/discovery.js.map +1 -0
- package/dist/agents/index.d.ts +7 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +20 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/openclaw/gatewayManager.d.ts +117 -0
- package/dist/agents/openclaw/gatewayManager.d.ts.map +1 -0
- package/dist/agents/openclaw/gatewayManager.js +486 -0
- package/dist/agents/openclaw/gatewayManager.js.map +1 -0
- package/dist/agents/openclaw/openclawProvider.d.ts.map +1 -0
- package/dist/agents/openclaw/openclawProvider.js +237 -0
- package/dist/agents/openclaw/openclawProvider.js.map +1 -0
- package/dist/agents/openclaw/sshTunnelManager.d.ts +25 -0
- package/dist/agents/openclaw/sshTunnelManager.d.ts.map +1 -0
- package/dist/agents/openclaw/sshTunnelManager.js +359 -0
- package/dist/agents/openclaw/sshTunnelManager.js.map +1 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +6 -0
- package/dist/agents/types.js.map +1 -0
- 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 +65 -96
- package/dist/connectors/registry.js.map +1 -1
- package/dist/connectors/types.d.ts.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 +17 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +75 -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 +97 -86
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +142 -145
- package/dist/migrations.js.map +1 -1
- package/dist/models/anthropic.d.ts +24 -0
- package/dist/models/anthropic.d.ts.map +1 -0
- package/dist/models/anthropic.js +103 -0
- package/dist/models/anthropic.js.map +1 -0
- package/dist/models/chainOfThought.d.ts.map +1 -0
- package/dist/models/chainOfThought.js +45 -0
- package/dist/models/chainOfThought.js.map +1 -0
- package/dist/models/fireworksai.d.ts.map +1 -0
- package/dist/models/fireworksai.js +141 -0
- package/dist/models/fireworksai.js.map +1 -0
- package/dist/models/index.d.ts +7 -1
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +20 -1
- package/dist/models/index.js.map +1 -1
- package/dist/models/logCompletePrompt.d.ts.map +1 -0
- package/dist/models/logCompletePrompt.js +23 -0
- package/dist/models/logCompletePrompt.js.map +1 -0
- package/dist/models/openai.d.ts +24 -0
- package/dist/models/openai.d.ts.map +1 -0
- package/dist/models/openai.js +101 -0
- package/dist/models/openai.js.map +1 -0
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/models/providers.js +12 -4
- package/dist/models/providers.js.map +1 -1
- package/dist/models/types.d.ts +53 -2
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js +21 -0
- package/dist/models/types.js.map +1 -1
- package/dist/models/utils.d.ts.map +1 -0
- package/dist/models/utils.js +21 -0
- package/dist/models/utils.js.map +1 -0
- 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/scripts.d.ts.map +1 -1
- package/dist/scripts.js +4 -3
- package/dist/scripts.js.map +1 -1
- package/dist/service/createCompletePrompt.d.ts.map +1 -1
- package/dist/service/createCompletePrompt.js +9 -6
- package/dist/service/createCompletePrompt.js.map +1 -1
- package/dist/service/generateImage.d.ts.map +1 -1
- package/dist/service/generateImage.js +3 -3
- package/dist/service/generateImage.js.map +1 -1
- package/dist/service/server.d.ts.map +1 -1
- package/dist/service/server.js +39 -7
- package/dist/service/server.js.map +1 -1
- package/dist/service/transformPage.d.ts +47 -18
- package/dist/service/transformPage.d.ts.map +1 -1
- package/dist/service/transformPage.js +559 -270
- package/dist/service/transformPage.js.map +1 -1
- package/dist/service/useAgentRoutes.d.ts +5 -0
- package/dist/service/useAgentRoutes.d.ts.map +1 -0
- package/dist/service/useAgentRoutes.js +392 -0
- package/dist/service/useAgentRoutes.js.map +1 -0
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +380 -138
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.d.ts.map +1 -1
- package/dist/service/useConnectorRoutes.js +20 -9
- 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 +660 -68
- 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 +3 -1
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +5 -8
- 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 +15 -0
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +106 -20
- 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 +15 -11
- package/required-pages/builder/page.html +43 -0
- package/required-pages/builder/page.json +10 -0
- package/required-pages/pages/page.html +924 -0
- package/required-pages/pages/page.json +10 -0
- package/required-pages/settings/page.html +1753 -0
- 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/service-connectors/airtable/connector.json +27 -0
- package/service-connectors/alpha-vantage/connector.json +26 -0
- package/service-connectors/brave-search/connector.json +26 -0
- package/service-connectors/cloudinary/connector.json +27 -0
- package/service-connectors/deepl/connector.json +28 -0
- package/service-connectors/elevenlabs/connector.json +30 -0
- package/service-connectors/giphy/connector.json +27 -0
- package/service-connectors/github/connector.json +29 -0
- package/service-connectors/huggingface/connector.json +27 -0
- package/service-connectors/imgur/connector.json +29 -0
- package/service-connectors/instagram/connector.json +43 -0
- package/service-connectors/jira/connector.json +28 -0
- package/service-connectors/mapbox/connector.json +26 -0
- package/service-connectors/nasa/connector.json +27 -0
- package/service-connectors/newsapi/connector.json +27 -0
- package/service-connectors/notion/connector.json +28 -0
- package/service-connectors/open-exchange-rates/connector.json +27 -0
- package/service-connectors/openweathermap/connector.json +26 -0
- package/service-connectors/pexels/connector.json +27 -0
- package/service-connectors/resend/connector.json +29 -0
- package/service-connectors/rss2json/connector.json +27 -0
- package/service-connectors/sendgrid/connector.json +27 -0
- package/service-connectors/spoonacular/connector.json +28 -0
- package/service-connectors/stability-ai/connector.json +27 -0
- package/service-connectors/twilio/connector.json +28 -0
- package/service-connectors/unsplash/connector.json +27 -0
- package/service-connectors/wolfram-alpha/connector.json +26 -0
- package/service-connectors/youtube-data/connector.json +30 -0
- package/src/agents/a2a/a2aProvider.ts +110 -0
- package/src/agents/discovery.ts +74 -0
- package/src/agents/index.ts +6 -0
- package/src/agents/openclaw/gatewayManager.ts +570 -0
- package/src/agents/openclaw/openclawProvider.ts +259 -0
- package/src/agents/openclaw/sshTunnelManager.ts +393 -0
- package/src/agents/types.ts +82 -0
- 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 +3 -1
- package/src/connectors/registry.ts +40 -96
- package/src/connectors/types.ts +25 -0
- package/src/customizer/Customizer.ts +151 -0
- package/src/customizer/index.ts +5 -0
- package/src/files.ts +71 -0
- package/src/index.ts +2 -1
- package/src/init.ts +138 -97
- package/src/migrations.ts +148 -145
- package/src/models/anthropic.ts +119 -0
- package/src/models/chainOfThought.ts +56 -0
- package/src/models/fireworksai.ts +143 -0
- package/src/models/index.ts +7 -1
- package/src/models/logCompletePrompt.ts +25 -0
- package/src/models/openai.ts +110 -0
- package/src/models/providers.ts +12 -3
- package/src/models/types.ts +97 -2
- package/src/models/utils.ts +16 -0
- package/src/pages.ts +176 -54
- package/src/scripts.ts +2 -2
- package/src/service/createCompletePrompt.ts +3 -1
- package/src/service/generateImage.ts +2 -2
- package/src/service/server.ts +39 -8
- package/src/service/transformPage.ts +605 -301
- package/src/service/useAgentRoutes.ts +428 -0
- package/src/service/useApiRoutes.ts +309 -45
- package/src/service/useConnectorRoutes.ts +21 -10
- package/src/service/useFileRoutes.ts +127 -0
- package/src/service/usePageRoutes.ts +736 -75
- package/src/service/useSharedDataRoutes.ts +106 -0
- package/src/service/useSharedFileRoutes.ts +126 -0
- package/src/settings.ts +8 -10
- package/src/synthos-cli.ts +4 -3
- package/src/themes.ts +103 -20
- 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/static-files/helpers.v3.js +304 -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/anthropic.spec.ts +84 -0
- package/tests/builders.spec.ts +139 -0
- package/tests/chainOfThought.spec.ts +108 -0
- package/tests/ensureScripts.spec.ts +82 -0
- package/tests/files.spec.ts +233 -0
- package/tests/fireworksai.spec.ts +92 -0
- package/tests/logCompletePrompt.spec.ts +74 -0
- package/tests/migrations.spec.ts +79 -1
- package/tests/openai.spec.ts +71 -0
- package/tests/pages.spec.ts +226 -1
- package/tests/providers.spec.ts +144 -0
- package/tests/scripts.spec.ts +209 -0
- package/tests/transformPage.spec.ts +456 -0
- package/tests/types.spec.ts +23 -0
- package/default-pages/app_builder.html +0 -40
- package/default-pages/app_builder.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_builder.json +0 -1
- package/default-pages/solar_tutorial.json +0 -1
- package/default-pages/two-panel_builder.json +0 -1
- package/dist/connectors/index.d.ts +0 -3
- package/dist/connectors/types.d.ts +0 -61
- package/dist/index.d.ts +0 -7
- package/dist/migrations.d.ts +0 -11
- package/dist/models/providers.d.ts +0 -7
- package/dist/scripts.d.ts +0 -14
- 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/usePageRoutes.d.ts +0 -5
- package/dist/synthos-cli.d.ts +0 -2
- package/images/home.png +0 -0
- package/images/page-management.png +0 -0
- package/images/settings.png +0 -0
- package/images/synthos-square.png +0 -0
- package/page-scripts/helpers-v2.js +0 -121
- package/page-scripts/page-v2.js +0 -615
- package/required-pages/builder.html +0 -74
- package/required-pages/builder.json +0 -1
- package/required-pages/pages.html +0 -196
- package/required-pages/pages.json +0 -1
- package/required-pages/settings.html +0 -841
- package/required-pages/settings.json +0 -1
- package/required-pages/synthos_apis.html +0 -272
- package/required-pages/synthos_apis.json +0 -1
- package/required-pages/synthos_scripts.json +0 -1
|
@@ -0,0 +1,1753 @@
|
|
|
1
|
+
<!DOCTYPE html><html lang="en"><head>
|
|
2
|
+
<meta charset="UTF-8">
|
|
3
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4
|
+
<title>SynthOS - Settings</title>
|
|
5
|
+
<script id="theme-info" src="/api/theme-info.js" data-locked="true"></script>
|
|
6
|
+
<link id="theme-css" rel="stylesheet" href="/api/theme.css" data-locked="true">
|
|
7
|
+
<style>
|
|
8
|
+
/* Layout-specific rules only — all component styling via FluentLM */
|
|
9
|
+
#settingsBtn { opacity: 0.4; pointer-events: none; }
|
|
10
|
+
.accordion-section { display: flex; flex-direction: column; flex-shrink: 0; overflow: hidden; border-bottom: 1px solid var(--neutralLight); }
|
|
11
|
+
.accordion-section:last-child { border-bottom: none; }
|
|
12
|
+
.accordion-section.active { flex-shrink: 1; flex-grow: 1; min-height: 0; }
|
|
13
|
+
.accordion-header { display: flex; justify-content: space-between; align-items: center; width: 100%; padding: 14px 20px; background: none; border: none; color: var(--bodyText); font-size: 15px; font-weight: 600; cursor: pointer; transition: background .2s; flex-shrink: 0; }
|
|
14
|
+
.accordion-header:hover { background: var(--defaultHoverBackground); }
|
|
15
|
+
.accordion-section.active .accordion-header { background: var(--defaultHoverBackground); }
|
|
16
|
+
.accordion-chevron { font-size: 11px; color: var(--bodySubtext); transition: transform .3s; }
|
|
17
|
+
.accordion-section.active .accordion-chevron { transform: rotate(180deg); }
|
|
18
|
+
.accordion-body { display: none; flex-direction: column; flex-grow: 1; min-height: 0; overflow: hidden; }
|
|
19
|
+
.accordion-section.active .accordion-body { display: flex; }
|
|
20
|
+
.accordion-content { display: flex; flex-direction: column; gap: 15px; overflow-y: auto; padding: 20px; flex-grow: 1; }
|
|
21
|
+
.button-row { display: flex; justify-content: flex-end; padding: 12px 20px; border-top: 1px solid var(--neutralLight); flex-shrink: 0; }
|
|
22
|
+
.wizard-hidden { display: none !important; }
|
|
23
|
+
.model-card { border: 1px solid var(--neutralLight); border-radius: 8px; padding: 16px; display: flex; flex-direction: column; gap: 12px; background: var(--defaultStateBackground); }
|
|
24
|
+
.model-card.disabled { opacity: 0.35; pointer-events: none; user-select: none; }
|
|
25
|
+
.accordion-section.disabled .accordion-header { opacity: 0.35; pointer-events: none; cursor: default; }
|
|
26
|
+
.connector-grid { display: flex; flex-wrap: wrap; gap: 12px; }
|
|
27
|
+
.connector-tile { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 18px 12px; border-radius: 12px; border: 1px solid var(--neutralLight); background: var(--defaultStateBackground); cursor: pointer; transition: background .2s, border-color .2s, box-shadow .2s, transform .15s; gap: 8px; min-width: 130px; }
|
|
28
|
+
.connector-tile:hover { background: var(--defaultHoverBackground); border-color: var(--themePrimary); box-shadow: 0 4px 12px rgba(0,0,0,.1); transform: translateY(-1px); }
|
|
29
|
+
.connector-tile.configured { border-color: var(--themePrimary); box-shadow: 0 2px 8px var(--themeLighter); }
|
|
30
|
+
.filter-bar { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }
|
|
31
|
+
.filter-buttons-container { display: flex; gap: 8px; flex-shrink: 1; min-width: 0; overflow: hidden; flex-wrap: nowrap; }
|
|
32
|
+
.more-dropdown { position: relative; flex-shrink: 0; }
|
|
33
|
+
.more-menu { position: absolute; top: 100%; left: 0; margin-top: 4px; min-width: 150px; max-height: 250px; overflow-y: auto; display: none; z-index: 100; }
|
|
34
|
+
.agent-chat-panel { border: 1px solid var(--neutralLight); border-radius: 8px; margin-top: 12px; overflow: hidden; display: none; }
|
|
35
|
+
.agent-chat-panel.open { display: flex; flex-direction: column; }
|
|
36
|
+
.agent-chat-header { display: flex; justify-content: space-between; align-items: center; padding: 10px 16px; background: var(--defaultHoverBackground); font-size: 13px; font-weight: 600; color: var(--bodyText); }
|
|
37
|
+
.agent-chat-messages { max-height: 250px; overflow-y: auto; padding: 12px 16px; display: flex; flex-direction: column; gap: 8px; font-size: 13px; }
|
|
38
|
+
.agent-chat-msg { padding: 8px 12px; border-radius: 10px; max-width: 85%; line-height: 1.5; word-wrap: break-word; white-space: pre-wrap; }
|
|
39
|
+
.agent-chat-msg.user { background: var(--themePrimary); color: #fff; align-self: flex-end; }
|
|
40
|
+
.agent-chat-msg.agent { background: var(--defaultStateBackground); color: var(--bodyText); align-self: flex-start; border: 1px solid var(--neutralLight); }
|
|
41
|
+
.agent-chat-msg.error { background: var(--errorBackground); color: var(--errorText); align-self: center; font-size: 12px; }
|
|
42
|
+
.agent-chat-msg.status { color: var(--bodySubtext); align-self: center; font-size: 12px; font-style: italic; }
|
|
43
|
+
.agent-chat-input-row { display: flex; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--neutralLight); }
|
|
44
|
+
</style>
|
|
45
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"></script>
|
|
46
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/14.1.1/marked.min.js"></script>
|
|
47
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.1.0/mermaid.min.js"></script>
|
|
48
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
|
49
|
+
</head>
|
|
50
|
+
<body>
|
|
51
|
+
<div class="shell-toolbar" data-locked="true">
|
|
52
|
+
<button class="shell-toolbar-btn" id="builderToggle" aria-label="Page Builder" data-locked="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"><path d="M7 18.5H6.2c-1.77 0-3.2-1.43-3.2-3.2V7.7C3 5.93 4.43 4.5 6.2 4.5h11.6c1.77 0 3.2 1.43 3.2 3.2v7.6c0 1.77-1.43 3.2-3.2 3.2H12l-4.2 3.2c-.5.38-1.2.02-1.2-.6V18.5Z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><circle cx="8.5" cy="11.5" r="1" fill="currentColor"/><circle cx="12" cy="11.5" r="1" fill="currentColor"/><circle cx="15.5" cy="11.5" r="1" fill="currentColor"/></svg></button>
|
|
53
|
+
<button class="shell-toolbar-btn" id="pagesBtn" aria-label="View All Pages" data-locked="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none"><rect x="3" y="3" width="11" height="12" rx="1.5" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M6 7.5h5M6 10h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><rect x="18" y="3" width="11" height="12" rx="1.5" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M21 7.5h5M21 10h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><rect x="3" y="18" width="11" height="12" rx="1.5" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M6 22.5h5M6 25h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><rect x="18" y="18" width="11" height="12" rx="1.5" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M21 22.5h5M21 25h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button>
|
|
54
|
+
<button class="shell-toolbar-btn" id="saveBtn" aria-label="Save Page" data-locked="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2Z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M17 21v-8H7v8" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M7 3v5h8" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg></button>
|
|
55
|
+
<div class="shell-toolbar-spacer" data-locked="true"></div>
|
|
56
|
+
<button class="shell-toolbar-btn" id="settingsBtn" aria-label="Settings" data-locked="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"><path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z" stroke="currentColor" stroke-width="1.8"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="chat-panel" data-locked="true">
|
|
59
|
+
<div class="chat-header" data-locked="true"><span>Page Builder</span><button class="chat-header-close" id="builderClose" aria-label="Close builder" data-locked="true">×</button></div>
|
|
60
|
+
<div class="chat-messages" id="chatMessages" data-locked="true">
|
|
61
|
+
<div class="chat-message" id="defaultGreeting">
|
|
62
|
+
<p><strong>SynthOS:</strong> This is where you can customize your experience. Change your theme and toolbar layout under <strong>Appearance</strong>, configure AI models under <strong>Page Building & Chat</strong>, or set up integrations under <strong>Connectors</strong> and <strong>Agents</strong>.</p>
|
|
63
|
+
<p>Click <strong>Apply</strong> when you're ready to save your changes.</p>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="chat-message" id="firstRunGreeting" style="display:none;">
|
|
66
|
+
<p><strong>SynthOS:</strong> Welcome to SynthOS! We're glad you're here.</p>
|
|
67
|
+
<p>Before you can start building, we need to connect to an AI provider. Just pick your provider below, paste in your API key, and you'll be ready to go.</p>
|
|
68
|
+
<p>You can always come back to this page later to change your theme or enable additional features.</p>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
<form action="/" method="POST" id="chatForm" data-locked="true">
|
|
72
|
+
<textarea class="chat-input" id="chatInput" name="message" rows="2" placeholder="Type a message..." data-locked="true"></textarea>
|
|
73
|
+
</form>
|
|
74
|
+
</div>
|
|
75
|
+
<div class="viewer-panel" id="viewerPanel" style="justify-content: flex-start; align-items: stretch;">
|
|
76
|
+
<div style="padding: 16px 20px; border-bottom: 1px solid var(--neutralLight);">
|
|
77
|
+
<span class="flm-text flm-text--xLarge flm-text--bold">Settings</span>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="flm-stack" style="flex-grow: 1; overflow: hidden;">
|
|
80
|
+
|
|
81
|
+
<div class="accordion-section active" data-section="appearance">
|
|
82
|
+
<button class="accordion-header">
|
|
83
|
+
<span>Appearance</span>
|
|
84
|
+
<span class="accordion-chevron">▾</span>
|
|
85
|
+
</button>
|
|
86
|
+
<div class="accordion-body">
|
|
87
|
+
<div class="accordion-content">
|
|
88
|
+
<div class="flm-textfield">
|
|
89
|
+
<label class="flm-label" for="theme">Theme</label>
|
|
90
|
+
<select id="theme" class="flm-textfield-input">
|
|
91
|
+
<option value="">Loading themes...</option>
|
|
92
|
+
</select>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="flm-textfield">
|
|
95
|
+
<label class="flm-label" for="toolbarPosition">Toolbar Position</label>
|
|
96
|
+
<select id="toolbarPosition" class="flm-textfield-input">
|
|
97
|
+
<option value="left">Left</option>
|
|
98
|
+
<option value="right">Right</option>
|
|
99
|
+
</select>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div class="accordion-section" data-section="models">
|
|
106
|
+
<button class="accordion-header">
|
|
107
|
+
<span>Page Building & Chat</span>
|
|
108
|
+
<span class="accordion-chevron">▾</span>
|
|
109
|
+
</button>
|
|
110
|
+
<div class="accordion-body">
|
|
111
|
+
<div class="accordion-content">
|
|
112
|
+
<div id="configBanner" class="flm-messagebar flm-messagebar--warning" style="display:none;">
|
|
113
|
+
Model configuration is required before you can use SynthOS. Please fill in the Page Builder fields below and click Apply.
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div class="model-card" id="builderCard">
|
|
117
|
+
<span class="flm-text flm-text--mediumPlus flm-text--bold">Page Builder Model</span>
|
|
118
|
+
<div class="flm-textfield">
|
|
119
|
+
<label class="flm-label" for="provider-builder">Provider</label>
|
|
120
|
+
<select id="provider-builder" class="flm-textfield-input" required="">
|
|
121
|
+
<option value="">Select a provider</option>
|
|
122
|
+
</select>
|
|
123
|
+
</div>
|
|
124
|
+
<div class="flm-textfield" id="fg-apikey-builder">
|
|
125
|
+
<label class="flm-label" for="serviceApiKey-builder">API Key</label>
|
|
126
|
+
<input type="password" id="serviceApiKey-builder" class="flm-textfield-input" placeholder="Enter your API Key" required="">
|
|
127
|
+
<div id="providerInstructions" class="flm-messagebar flm-messagebar--info" style="display:none;margin-top:8px;"></div>
|
|
128
|
+
</div>
|
|
129
|
+
<div class="flm-textfield" id="fg-model-builder">
|
|
130
|
+
<label class="flm-label" for="model-builder">Model</label>
|
|
131
|
+
<select id="model-builder" class="flm-textfield-input" required="">
|
|
132
|
+
<option value="">Select a model</option>
|
|
133
|
+
</select>
|
|
134
|
+
</div>
|
|
135
|
+
<a id="moreSettingsLink" class="flm-link" style="font-size:13px;">▾ More settings</a>
|
|
136
|
+
<div class="flm-textfield" id="fg-maxTokens-builder">
|
|
137
|
+
<label class="flm-label" for="maxTokens-builder">Max Output Tokens</label>
|
|
138
|
+
<input type="number" id="maxTokens-builder" class="flm-textfield-input" placeholder="Enter max token count" required="">
|
|
139
|
+
</div>
|
|
140
|
+
<div class="flm-textfield" id="fg-instructions-builder">
|
|
141
|
+
<label class="flm-label" for="instructions-builder">Additional Instructions</label>
|
|
142
|
+
<textarea id="instructions-builder" class="flm-textfield-input" placeholder="Enter any additional instructions"></textarea>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div class="model-card" id="chatCard">
|
|
147
|
+
<span class="flm-text flm-text--mediumPlus flm-text--bold">Chat Model</span>
|
|
148
|
+
<div class="flm-textfield" id="fg-provider-chat">
|
|
149
|
+
<label class="flm-label" for="provider-chat">Provider</label>
|
|
150
|
+
<select id="provider-chat" class="flm-textfield-input" required="">
|
|
151
|
+
<option value="">Select a provider</option>
|
|
152
|
+
</select>
|
|
153
|
+
</div>
|
|
154
|
+
<div class="flm-textfield" id="fg-apikey-chat">
|
|
155
|
+
<label class="flm-label" for="serviceApiKey-chat">API Key</label>
|
|
156
|
+
<input type="password" id="serviceApiKey-chat" class="flm-textfield-input" placeholder="Enter your API Key" required="">
|
|
157
|
+
</div>
|
|
158
|
+
<div class="flm-textfield" id="fg-model-chat">
|
|
159
|
+
<label class="flm-label" for="model-chat">Model</label>
|
|
160
|
+
<select id="model-chat" class="flm-textfield-input" required="">
|
|
161
|
+
<option value="">Select a model</option>
|
|
162
|
+
</select>
|
|
163
|
+
</div>
|
|
164
|
+
<a id="moreSettingsLinkChat" class="flm-link" style="font-size:13px;">▾ More settings</a>
|
|
165
|
+
<div class="flm-textfield" id="fg-maxTokens-chat">
|
|
166
|
+
<label class="flm-label" for="maxTokens-chat">Max Output Tokens</label>
|
|
167
|
+
<input type="number" id="maxTokens-chat" class="flm-textfield-input" placeholder="Enter max token count" required="">
|
|
168
|
+
</div>
|
|
169
|
+
<div class="flm-textfield" id="fg-instructions-chat">
|
|
170
|
+
<label class="flm-label" for="instructions-chat">Additional Instructions</label>
|
|
171
|
+
<textarea id="instructions-chat" class="flm-textfield-input" placeholder="Enter any additional instructions"></textarea>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div class="accordion-section" data-section="connectors">
|
|
180
|
+
<button class="accordion-header">
|
|
181
|
+
<span>Connectors <span style="display:inline-block;margin-left:6px;padding:1px 7px;font-size:10px;font-weight:600;letter-spacing:.5px;border-radius:6px;background:var(--themePrimary);color:#fff;vertical-align:middle;line-height:16px;text-transform:uppercase;">Preview</span></span>
|
|
182
|
+
<span class="accordion-chevron">▾</span>
|
|
183
|
+
</button>
|
|
184
|
+
<div class="accordion-body">
|
|
185
|
+
<div class="accordion-content">
|
|
186
|
+
<div class="filter-bar" id="connectorFilterBar">
|
|
187
|
+
<div class="filter-buttons-container" id="connectorFilterButtons"></div>
|
|
188
|
+
<div class="more-dropdown" id="connMoreDropdown" style="display:none;">
|
|
189
|
+
<button class="flm-button flm-button--subtle" id="connMoreBtn">More <i class="flm-icon" data-icon="ChevronDown"></i></button>
|
|
190
|
+
<div class="flm-contextmenu more-menu" id="connMoreMenu"></div>
|
|
191
|
+
</div>
|
|
192
|
+
<div class="flm-searchbox" id="connectorSearchBox">
|
|
193
|
+
<input class="flm-searchbox-input" type="text" id="connectorSearch" placeholder="Search connectors...">
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
<div class="connector-grid" id="connectorGrid"></div>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
<div class="accordion-section" data-section="agents">
|
|
202
|
+
<button class="accordion-header">
|
|
203
|
+
<span>Agents <span style="display:inline-block;margin-left:6px;padding:1px 7px;font-size:10px;font-weight:600;letter-spacing:.5px;border-radius:6px;background:var(--themePrimary);color:#fff;vertical-align:middle;line-height:16px;text-transform:uppercase;">Preview</span></span>
|
|
204
|
+
<span class="accordion-chevron">▾</span>
|
|
205
|
+
</button>
|
|
206
|
+
<div class="accordion-body">
|
|
207
|
+
<div class="accordion-content">
|
|
208
|
+
<div class="flm-stack flm-stack--horizontal flm-stack--space-between" style="align-items:center;margin-bottom:12px;">
|
|
209
|
+
<span class="flm-text flm-text--small flm-text--secondary">Configure agents (A2A or OpenClaw) that your pages can communicate with.</span>
|
|
210
|
+
<button class="flm-button flm-button--primary" id="addAgentBtn" style="white-space:nowrap;">+ Add Agent</button>
|
|
211
|
+
</div>
|
|
212
|
+
<div class="flm-list flm-list--bordered" id="agentList"></div>
|
|
213
|
+
<div id="agentEmptyState" style="display:none;text-align:center;padding:30px;color:var(--bodySubtext);font-size:14px;">
|
|
214
|
+
No agents configured yet. Click "+ Add Agent" to get started.
|
|
215
|
+
</div>
|
|
216
|
+
<div class="agent-chat-panel" id="agentChatPanel">
|
|
217
|
+
<div class="agent-chat-header">
|
|
218
|
+
<span id="agentChatTitle">Chat with Agent</span>
|
|
219
|
+
<button class="flm-button flm-button--subtle flm-button--icon" id="agentChatClose" aria-label="Close chat" data-icon="Cancel"></button>
|
|
220
|
+
</div>
|
|
221
|
+
<div class="agent-chat-messages" id="agentChatMessages"></div>
|
|
222
|
+
<div class="agent-chat-input-row">
|
|
223
|
+
<input type="text" class="flm-textfield-input" id="agentChatInput" placeholder="Type a test message..." style="flex:1;">
|
|
224
|
+
<button class="flm-button flm-button--primary" id="agentChatSendBtn">Send</button>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
</div>
|
|
232
|
+
<div class="button-row" id="applyRow">
|
|
233
|
+
<button class="flm-button" id="applyBtn">Apply</button>
|
|
234
|
+
</div>
|
|
235
|
+
<div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div>
|
|
236
|
+
</div>
|
|
237
|
+
<div class="flm-dialog-overlay" id="agentModal">
|
|
238
|
+
<div class="flm-dialog" style="max-width:520px;width:90%;max-height:85vh;overflow-y:auto;">
|
|
239
|
+
<div class="flm-dialog-header">
|
|
240
|
+
<h2 class="flm-dialog-title" id="agentModalTitle">Add Agent</h2>
|
|
241
|
+
</div>
|
|
242
|
+
<div class="flm-dialog-body">
|
|
243
|
+
<div class="flm-stack" style="gap:10px;" id="agentFormGroup">
|
|
244
|
+
<div class="flm-textfield">
|
|
245
|
+
<label class="flm-label">Agent Type</label>
|
|
246
|
+
<div class="flm-stack flm-stack--horizontal" style="gap:8px;">
|
|
247
|
+
<button class="flm-pivot-tab flm-pivot-tab--active" id="agentTypeA2A">A2A Agent</button>
|
|
248
|
+
<button class="flm-pivot-tab" id="agentTypeOpenClaw">OpenClaw Agent</button>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
<div class="flm-textfield">
|
|
252
|
+
<label class="flm-label" for="agentUrl">Agent URL</label>
|
|
253
|
+
<div class="flm-stack flm-stack--horizontal" style="gap:8px;">
|
|
254
|
+
<input type="text" id="agentUrl" class="flm-textfield-input" placeholder="https://example.com" style="flex:1;">
|
|
255
|
+
<button class="flm-button flm-button--primary" id="agentDiscoverBtn" style="white-space:nowrap;">Discover</button>
|
|
256
|
+
</div>
|
|
257
|
+
<span id="agentUrlHint" class="flm-text flm-text--small flm-text--secondary" style="margin-top:4px;">Enter a URL and click Discover to auto-fill from the agent card, or fill in the fields manually.</span>
|
|
258
|
+
</div>
|
|
259
|
+
<div class="flm-textfield" id="agentTokenGroup" style="display:none;">
|
|
260
|
+
<label class="flm-label" for="agentToken">Token</label>
|
|
261
|
+
<input type="password" id="agentToken" class="flm-textfield-input" placeholder="Gateway authentication token">
|
|
262
|
+
</div>
|
|
263
|
+
<div class="flm-textfield" id="agentSessionKeyGroup" style="display:none;">
|
|
264
|
+
<label class="flm-label" for="agentSessionKey">Default Session Key</label>
|
|
265
|
+
<input type="text" id="agentSessionKey" class="flm-textfield-input" placeholder="e.g. agent:main:main">
|
|
266
|
+
</div>
|
|
267
|
+
<div id="agentSshTunnelGroup" style="display:none;">
|
|
268
|
+
<div class="flm-stack flm-stack--horizontal" style="align-items:center;gap:8px;cursor:pointer;margin-bottom:8px;" id="agentSshTunnelToggleHeader">
|
|
269
|
+
<span class="flm-text flm-text--small flm-text--secondary" id="agentSshTunnelArrow">▶</span>
|
|
270
|
+
<label class="flm-label" style="margin:0;cursor:pointer;">SSH Tunnel</label>
|
|
271
|
+
<label class="flm-toggle flm-toggle--inline" style="margin-left:auto;" onclick="event.stopPropagation();">
|
|
272
|
+
<input type="checkbox" class="flm-toggle-input" id="agentSshTunnelEnabled">
|
|
273
|
+
<span class="flm-toggle-track"><span class="flm-toggle-thumb"></span></span>
|
|
274
|
+
</label>
|
|
275
|
+
</div>
|
|
276
|
+
<div id="agentSshTunnelFields" style="display:none;padding-left:4px;" class="flm-stack" >
|
|
277
|
+
<div class="flm-textfield" style="margin-bottom:8px;">
|
|
278
|
+
<label class="flm-label" for="agentSshCommand" style="font-size:12px;">SSH Command</label>
|
|
279
|
+
<input type="text" id="agentSshCommand" class="flm-textfield-input" placeholder="ssh -p 22 -N -L 18789:127.0.0.1:43901 root@0.0.0.0">
|
|
280
|
+
</div>
|
|
281
|
+
<div class="flm-textfield" style="margin-bottom:8px;">
|
|
282
|
+
<label class="flm-label" for="agentSshPassword" style="font-size:12px;">Password</label>
|
|
283
|
+
<input type="password" id="agentSshPassword" class="flm-textfield-input" placeholder="SSH password">
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
<div class="flm-textfield">
|
|
288
|
+
<label class="flm-label" for="agentName">Name</label>
|
|
289
|
+
<input type="text" id="agentName" class="flm-textfield-input" placeholder="My Agent">
|
|
290
|
+
</div>
|
|
291
|
+
<div class="flm-textfield">
|
|
292
|
+
<label class="flm-label" for="agentDescription">Description</label>
|
|
293
|
+
<textarea id="agentDescription" class="flm-textfield-input" placeholder="Describe what this agent does and when to use it..." style="min-height:60px;"></textarea>
|
|
294
|
+
</div>
|
|
295
|
+
<div id="agentSkillsPreview" style="display:none;padding:10px;border-radius:8px;border:1px solid var(--neutralLight);background:var(--defaultStateBackground);">
|
|
296
|
+
<span class="flm-text flm-text--small flm-text--semibold flm-text--secondary" style="margin-bottom:4px;display:block;">Discovered Skills</span>
|
|
297
|
+
<div id="agentSkillsList" class="flm-text flm-text--small flm-text--secondary"></div>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
<div class="flm-dialog-footer">
|
|
302
|
+
<button class="flm-button flm-button--primary" id="agentSaveBtn">Save</button>
|
|
303
|
+
<button class="flm-button" id="agentCancelBtn">Cancel</button>
|
|
304
|
+
<button class="flm-button" id="agentRemoveBtn" style="display:none;color:var(--errorText);">Remove</button>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
<div class="flm-dialog-overlay" id="connectorModal">
|
|
309
|
+
<div class="flm-dialog" style="max-width:520px;width:90%;max-height:85vh;overflow-y:auto;">
|
|
310
|
+
<div class="flm-dialog-header">
|
|
311
|
+
<h2 class="flm-dialog-title" id="connectorModalTitle"></h2>
|
|
312
|
+
</div>
|
|
313
|
+
<div class="flm-dialog-body">
|
|
314
|
+
<div class="flm-stack" style="gap:12px;">
|
|
315
|
+
<span class="flm-text flm-text--small flm-text--secondary" id="connectorModalDesc"></span>
|
|
316
|
+
<div id="connectorOnboarding" style="display:none;">
|
|
317
|
+
<a id="connectorOnboardingLink" class="flm-link" href="#" target="_blank">
|
|
318
|
+
Get your API key →
|
|
319
|
+
</a>
|
|
320
|
+
<ol id="connectorOnboardingSteps"
|
|
321
|
+
style="font-size:13px;color:var(--bodySubtext);margin:8px 0 0;padding-left:20px;line-height:1.8;">
|
|
322
|
+
</ol>
|
|
323
|
+
</div>
|
|
324
|
+
<div id="connectorApiKeyGroup" class="flm-textfield">
|
|
325
|
+
<label class="flm-label" for="connectorApiKey">API Key</label>
|
|
326
|
+
<input type="password" id="connectorApiKey" class="flm-textfield-input" placeholder="Enter your API key">
|
|
327
|
+
</div>
|
|
328
|
+
<div id="connectorOAuthGroup" style="display:none;">
|
|
329
|
+
<div id="connectorOAuthStatus" style="margin-bottom:12px;font-size:13px;line-height:1.5;"></div>
|
|
330
|
+
<div id="connectorOAuthModeToggle" class="flm-stack flm-stack--horizontal" style="gap:8px;margin-bottom:12px;">
|
|
331
|
+
<button class="flm-pivot-tab flm-pivot-tab--active" id="oauthModeManual">Manual Token</button>
|
|
332
|
+
<button class="flm-pivot-tab" id="oauthModeApp">OAuth App</button>
|
|
333
|
+
</div>
|
|
334
|
+
<div id="connectorManualGroup">
|
|
335
|
+
<div class="flm-textfield" style="margin-bottom:10px;">
|
|
336
|
+
<label class="flm-label" for="oauth-field-accessToken">Access Token</label>
|
|
337
|
+
<input type="password" id="oauth-field-accessToken" class="flm-textfield-input" placeholder="Paste token from Graph API Explorer">
|
|
338
|
+
</div>
|
|
339
|
+
<div class="flm-textfield" style="margin-bottom:10px;">
|
|
340
|
+
<label class="flm-label" for="oauth-field-userId">IG User ID <span class="flm-text--secondary" style="font-weight:400;">(optional)</span></label>
|
|
341
|
+
<input type="text" id="oauth-field-userId" class="flm-textfield-input" placeholder="Instagram Business Account ID">
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
<div id="connectorAppGroup" style="display:none;">
|
|
345
|
+
<div id="connectorOAuthFields" class="flm-stack" style="gap:10px;"></div>
|
|
346
|
+
<button class="flm-button flm-button--primary" id="connectorConnectBtn" style="width:100%;margin-top:8px;margin-bottom:8px;">Connect with OAuth</button>
|
|
347
|
+
</div>
|
|
348
|
+
<button class="flm-button" id="connectorDisconnectBtn" style="width:100%;display:none;color:var(--errorText);">Disconnect</button>
|
|
349
|
+
</div>
|
|
350
|
+
<div class="flm-stack flm-stack--horizontal" style="align-items:center;gap:12px;">
|
|
351
|
+
<label class="flm-label" for="connectorEnabled" style="margin-bottom:0;">Enabled</label>
|
|
352
|
+
<label class="flm-toggle flm-toggle--inline">
|
|
353
|
+
<input type="checkbox" class="flm-toggle-input" id="connectorEnabled">
|
|
354
|
+
<span class="flm-toggle-track"><span class="flm-toggle-thumb"></span></span>
|
|
355
|
+
</label>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
<div class="flm-dialog-footer">
|
|
360
|
+
<button class="flm-button flm-button--primary" id="connectorSaveBtn">Save</button>
|
|
361
|
+
<button class="flm-button" id="connectorCancelBtn">Cancel</button>
|
|
362
|
+
<button class="flm-button" id="connectorRemoveBtn" style="color:var(--errorText);">Remove</button>
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
<div class="flm-dialog-overlay" id="unsavedDialog" data-light-dismiss>
|
|
367
|
+
<div class="flm-dialog" style="max-width:400px;width:90%;">
|
|
368
|
+
<div class="flm-dialog-header">
|
|
369
|
+
<h2 class="flm-dialog-title">Unsaved Changes</h2>
|
|
370
|
+
</div>
|
|
371
|
+
<div class="flm-dialog-body">
|
|
372
|
+
<p class="flm-text">You have unsaved changes. Do you want to discard them and leave this page?</p>
|
|
373
|
+
</div>
|
|
374
|
+
<div class="flm-dialog-footer">
|
|
375
|
+
<div style="flex:1;"></div>
|
|
376
|
+
<button class="flm-button" id="unsavedStayBtn">Stay</button>
|
|
377
|
+
<button class="flm-button" id="unsavedLeaveBtn" style="color:var(--errorText);">Discard & Leave</button>
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
<div id="instructionsHidden" style="display: none;" data-locked="true"></div>
|
|
382
|
+
<div id="thoughts" style="display: none;" data-locked="true"></div>
|
|
383
|
+
<script id="settings-logic">
|
|
384
|
+
// --- Accordion logic ---
|
|
385
|
+
function openSection(sectionName) {
|
|
386
|
+
document.querySelectorAll('.accordion-section').forEach(function(s) {
|
|
387
|
+
s.classList.remove('active');
|
|
388
|
+
});
|
|
389
|
+
var target = document.querySelector('.accordion-section[data-section="' + sectionName + '"]');
|
|
390
|
+
if (target) target.classList.add('active');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
document.querySelectorAll('.accordion-header').forEach(function(header) {
|
|
394
|
+
header.addEventListener('click', function() {
|
|
395
|
+
var section = header.closest('.accordion-section');
|
|
396
|
+
if (section.classList.contains('disabled')) return;
|
|
397
|
+
var sectionName = section.dataset.section;
|
|
398
|
+
var isActive = section.classList.contains('active');
|
|
399
|
+
document.querySelectorAll('.accordion-section').forEach(function(s) {
|
|
400
|
+
s.classList.remove('active');
|
|
401
|
+
});
|
|
402
|
+
if (!isActive) {
|
|
403
|
+
section.classList.add('active');
|
|
404
|
+
// Update URL without reload
|
|
405
|
+
var url = new URL(window.location);
|
|
406
|
+
url.searchParams.set('tab', sectionName);
|
|
407
|
+
history.replaceState(null, '', url);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// --- URL param: open requested tab ---
|
|
413
|
+
var params = new URLSearchParams(window.location.search);
|
|
414
|
+
var tabParam = params.get('tab');
|
|
415
|
+
if (tabParam && ['appearance', 'models', 'connectors', 'agents'].indexOf(tabParam) !== -1) {
|
|
416
|
+
openSection(tabParam);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// --- Connectors logic ---
|
|
420
|
+
var connectorList = [], activeConnectorCategory = 'All', currentConnectorId = null;
|
|
421
|
+
var connVisibleCats = [], connOverflowCats = [];
|
|
422
|
+
|
|
423
|
+
// MRU: most-recently-used categories get priority for visible slots
|
|
424
|
+
var connMru = (function() {
|
|
425
|
+
try { return JSON.parse(localStorage.getItem('synthos_connMru')) || []; }
|
|
426
|
+
catch(e) { return []; }
|
|
427
|
+
})();
|
|
428
|
+
function connMruTouch(cat) {
|
|
429
|
+
if (cat === 'All' || cat === 'Enabled') return;
|
|
430
|
+
connMru = connMru.filter(function(c) { return c !== cat; });
|
|
431
|
+
connMru.unshift(cat);
|
|
432
|
+
if (connMru.length > 20) connMru.length = 20;
|
|
433
|
+
try { localStorage.setItem('synthos_connMru', JSON.stringify(connMru)); } catch(e) {}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function getAllConnectorCategories() {
|
|
437
|
+
var cats = new Set();
|
|
438
|
+
connectorList.forEach(function(c) { cats.add(c.category); });
|
|
439
|
+
return Array.from(cats).sort();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function calculateConnectorVisibleCats() {
|
|
443
|
+
var allCats = getAllConnectorCategories();
|
|
444
|
+
var filterBar = document.getElementById('connectorFilterBar');
|
|
445
|
+
var searchEl = document.getElementById('connectorSearchBox');
|
|
446
|
+
var moreDropdown = document.getElementById('connMoreDropdown');
|
|
447
|
+
if (!filterBar) return;
|
|
448
|
+
|
|
449
|
+
var availableWidth = filterBar.offsetWidth - (searchEl.offsetWidth + 8) - 16;
|
|
450
|
+
|
|
451
|
+
// Measure button widths
|
|
452
|
+
var temp = document.createElement('button');
|
|
453
|
+
temp.className = 'flm-pivot-tab';
|
|
454
|
+
temp.style.visibility = 'hidden';
|
|
455
|
+
temp.style.position = 'absolute';
|
|
456
|
+
document.body.appendChild(temp);
|
|
457
|
+
|
|
458
|
+
// Measure "All" button
|
|
459
|
+
temp.textContent = 'All';
|
|
460
|
+
var allBtnWidth = temp.offsetWidth + 8;
|
|
461
|
+
|
|
462
|
+
// Measure "Enabled" button (always shown)
|
|
463
|
+
temp.textContent = 'Enabled';
|
|
464
|
+
var enabledBtnWidth = temp.offsetWidth + 8;
|
|
465
|
+
|
|
466
|
+
// Measure "More" button
|
|
467
|
+
temp.textContent = 'More \u25BE';
|
|
468
|
+
var moreBtnWidth = temp.offsetWidth + 8;
|
|
469
|
+
|
|
470
|
+
// Measure each category button
|
|
471
|
+
var catWidths = {};
|
|
472
|
+
allCats.forEach(function(cat) {
|
|
473
|
+
temp.textContent = cat;
|
|
474
|
+
catWidths[cat] = temp.offsetWidth + 8;
|
|
475
|
+
});
|
|
476
|
+
document.body.removeChild(temp);
|
|
477
|
+
|
|
478
|
+
// Sort categories: MRU first, then alphabetical
|
|
479
|
+
var sorted = allCats.slice().sort(function(a, b) {
|
|
480
|
+
var ai = connMru.indexOf(a);
|
|
481
|
+
var bi = connMru.indexOf(b);
|
|
482
|
+
var aInMru = ai !== -1;
|
|
483
|
+
var bInMru = bi !== -1;
|
|
484
|
+
if (aInMru && !bInMru) return -1;
|
|
485
|
+
if (!aInMru && bInMru) return 1;
|
|
486
|
+
if (aInMru && bInMru) return ai - bi;
|
|
487
|
+
return a.localeCompare(b);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
connVisibleCats = [];
|
|
491
|
+
connOverflowCats = [];
|
|
492
|
+
var usedWidth = allBtnWidth + enabledBtnWidth;
|
|
493
|
+
var overflowing = false;
|
|
494
|
+
|
|
495
|
+
for (var i = 0; i < sorted.length; i++) {
|
|
496
|
+
var cat = sorted[i];
|
|
497
|
+
var remaining = sorted.length - i;
|
|
498
|
+
// When we'd start overflowing, reserve space for the More button
|
|
499
|
+
var widthIfAdded = usedWidth + catWidths[cat];
|
|
500
|
+
if (!overflowing && remaining > 1) widthIfAdded += moreBtnWidth;
|
|
501
|
+
|
|
502
|
+
if (!overflowing && usedWidth + catWidths[cat] <= availableWidth - (remaining > 1 ? moreBtnWidth : 0)) {
|
|
503
|
+
connVisibleCats.push(cat);
|
|
504
|
+
usedWidth += catWidths[cat];
|
|
505
|
+
} else {
|
|
506
|
+
overflowing = true;
|
|
507
|
+
connOverflowCats.push(cat);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// If only one overflow, try to fit it without the More button
|
|
512
|
+
if (connOverflowCats.length === 1) {
|
|
513
|
+
if (usedWidth + catWidths[connOverflowCats[0]] <= availableWidth) {
|
|
514
|
+
connVisibleCats.push(connOverflowCats.pop());
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function renderConnectorFilterBar() {
|
|
520
|
+
calculateConnectorVisibleCats();
|
|
521
|
+
var container = document.getElementById('connectorFilterButtons');
|
|
522
|
+
var moreDropdown = document.getElementById('connMoreDropdown');
|
|
523
|
+
var moreBtn = document.getElementById('connMoreBtn');
|
|
524
|
+
var moreMenu = document.getElementById('connMoreMenu');
|
|
525
|
+
container.innerHTML = '';
|
|
526
|
+
moreMenu.innerHTML = '';
|
|
527
|
+
|
|
528
|
+
// "All" button
|
|
529
|
+
var allBtn = document.createElement('button');
|
|
530
|
+
allBtn.className = 'flm-pivot-tab' + (activeConnectorCategory === 'All' ? ' flm-pivot-tab--active' : '');
|
|
531
|
+
allBtn.textContent = 'All';
|
|
532
|
+
allBtn.addEventListener('click', function() { setConnectorCategory('All'); });
|
|
533
|
+
container.appendChild(allBtn);
|
|
534
|
+
|
|
535
|
+
// "Enabled" button (fixed, always visible)
|
|
536
|
+
var enabledBtn = document.createElement('button');
|
|
537
|
+
enabledBtn.className = 'flm-pivot-tab' + (activeConnectorCategory === 'Enabled' ? ' flm-pivot-tab--active' : '');
|
|
538
|
+
enabledBtn.textContent = 'Enabled';
|
|
539
|
+
enabledBtn.addEventListener('click', function() { setConnectorCategory('Enabled'); });
|
|
540
|
+
container.appendChild(enabledBtn);
|
|
541
|
+
|
|
542
|
+
// Visible category buttons
|
|
543
|
+
connVisibleCats.forEach(function(cat) {
|
|
544
|
+
var btn = document.createElement('button');
|
|
545
|
+
btn.className = 'flm-pivot-tab' + (activeConnectorCategory === cat ? ' flm-pivot-tab--active' : '');
|
|
546
|
+
btn.textContent = cat;
|
|
547
|
+
btn.addEventListener('click', function() { setConnectorCategory(cat); });
|
|
548
|
+
container.appendChild(btn);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// Overflow dropdown
|
|
552
|
+
if (connOverflowCats.length > 0) {
|
|
553
|
+
moreDropdown.style.display = '';
|
|
554
|
+
moreBtn.className = 'flm-button flm-button--subtle' + (connOverflowCats.indexOf(activeConnectorCategory) !== -1 ? ' flm-pivot-tab--active' : '');
|
|
555
|
+
connOverflowCats.forEach(function(cat) {
|
|
556
|
+
var item = document.createElement('button');
|
|
557
|
+
item.className = 'flm-contextmenu-item' + (activeConnectorCategory === cat ? ' flm-contextmenu-item--checked' : '');
|
|
558
|
+
var itemText = document.createElement('span');
|
|
559
|
+
itemText.className = 'flm-contextmenu-item-text';
|
|
560
|
+
itemText.textContent = cat;
|
|
561
|
+
item.appendChild(itemText);
|
|
562
|
+
item.addEventListener('click', function() {
|
|
563
|
+
setConnectorCategory(cat);
|
|
564
|
+
moreMenu.classList.remove('flm-contextmenu--visible');
|
|
565
|
+
});
|
|
566
|
+
moreMenu.appendChild(item);
|
|
567
|
+
});
|
|
568
|
+
} else {
|
|
569
|
+
moreDropdown.style.display = 'none';
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function setConnectorCategory(cat) {
|
|
574
|
+
activeConnectorCategory = cat;
|
|
575
|
+
connMruTouch(cat);
|
|
576
|
+
renderConnectorFilterBar();
|
|
577
|
+
renderConnectorGrid();
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function loadConnectors() {
|
|
581
|
+
fetch('/api/connectors').then(function(r) { return r.json(); }).then(function(list) {
|
|
582
|
+
connectorList = list;
|
|
583
|
+
renderConnectorFilterBar();
|
|
584
|
+
renderConnectorGrid();
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function renderConnectorGrid() {
|
|
589
|
+
var grid = document.getElementById('connectorGrid');
|
|
590
|
+
var searchTerm = (document.getElementById('connectorSearch').value || '').toLowerCase();
|
|
591
|
+
grid.innerHTML = '';
|
|
592
|
+
connectorList.filter(function(c) {
|
|
593
|
+
if (activeConnectorCategory === 'Enabled') {
|
|
594
|
+
if (!c.configured) return false;
|
|
595
|
+
} else if (activeConnectorCategory !== 'All' && c.category !== activeConnectorCategory) {
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
if (searchTerm && c.name.toLowerCase().indexOf(searchTerm) === -1) return false;
|
|
599
|
+
return true;
|
|
600
|
+
}).forEach(function(c) {
|
|
601
|
+
var tile = document.createElement('div');
|
|
602
|
+
tile.className = 'connector-tile' + (c.configured ? ' configured' : '');
|
|
603
|
+
tile.setAttribute('data-tooltip', c.description);
|
|
604
|
+
tile.addEventListener('click', function() { openConnectorModal(c.id); });
|
|
605
|
+
|
|
606
|
+
var name = document.createElement('div');
|
|
607
|
+
name.className = 'flm-text flm-text--semibold';
|
|
608
|
+
name.textContent = c.name;
|
|
609
|
+
|
|
610
|
+
var cat = document.createElement('span');
|
|
611
|
+
cat.className = 'flm-text flm-text--small flm-text--secondary';
|
|
612
|
+
cat.style.cssText = 'background:var(--defaultHoverBackground);padding:2px 8px;border-radius:10px;';
|
|
613
|
+
cat.textContent = c.category;
|
|
614
|
+
|
|
615
|
+
tile.appendChild(name);
|
|
616
|
+
tile.appendChild(cat);
|
|
617
|
+
grid.appendChild(tile);
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
var currentConnectorDetail = null;
|
|
622
|
+
|
|
623
|
+
function openConnectorModal(id) {
|
|
624
|
+
currentConnectorId = id;
|
|
625
|
+
fetch('/api/connectors/' + encodeURIComponent(id)).then(function(r) { return r.json(); }).then(function(detail) {
|
|
626
|
+
currentConnectorDetail = detail;
|
|
627
|
+
document.getElementById('connectorModalTitle').textContent = detail.name;
|
|
628
|
+
document.getElementById('connectorModalDesc').textContent = detail.description;
|
|
629
|
+
|
|
630
|
+
var onboardingEl = document.getElementById('connectorOnboarding');
|
|
631
|
+
if (detail.onboarding && detail.onboarding.url) {
|
|
632
|
+
document.getElementById('connectorOnboardingLink').href = detail.onboarding.url;
|
|
633
|
+
var stepsOl = document.getElementById('connectorOnboardingSteps');
|
|
634
|
+
stepsOl.innerHTML = (detail.onboarding.steps || []).map(function(s) {
|
|
635
|
+
return '<li>' + s + '</li>';
|
|
636
|
+
}).join('');
|
|
637
|
+
onboardingEl.style.display = '';
|
|
638
|
+
} else {
|
|
639
|
+
onboardingEl.style.display = 'none';
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
document.getElementById('connectorEnabled').checked = detail.enabled;
|
|
643
|
+
document.getElementById('connectorRemoveBtn').style.display = (detail.configured || detail.hasKey) ? '' : 'none';
|
|
644
|
+
|
|
645
|
+
var isOAuth = detail.authStrategy === 'oauth2';
|
|
646
|
+
document.getElementById('connectorApiKeyGroup').style.display = isOAuth ? 'none' : '';
|
|
647
|
+
document.getElementById('connectorOAuthGroup').style.display = isOAuth ? '' : 'none';
|
|
648
|
+
|
|
649
|
+
if (isOAuth) {
|
|
650
|
+
// Render OAuth App fields (App ID, App Secret)
|
|
651
|
+
var fieldsContainer = document.getElementById('connectorOAuthFields');
|
|
652
|
+
fieldsContainer.innerHTML = '';
|
|
653
|
+
(detail.fields || []).forEach(function(f) {
|
|
654
|
+
var wrap = document.createElement('div');
|
|
655
|
+
wrap.className = 'flm-textfield';
|
|
656
|
+
var lbl = document.createElement('label');
|
|
657
|
+
lbl.className = 'flm-label';
|
|
658
|
+
lbl.textContent = f.label;
|
|
659
|
+
lbl.setAttribute('for', 'oauth-field-' + f.name);
|
|
660
|
+
var inp = document.createElement('input');
|
|
661
|
+
inp.type = f.type;
|
|
662
|
+
inp.id = 'oauth-field-' + f.name;
|
|
663
|
+
inp.className = 'flm-textfield-input';
|
|
664
|
+
inp.placeholder = detail.hasKey ? '(saved — leave blank to keep)' : 'Enter ' + f.label;
|
|
665
|
+
wrap.appendChild(lbl);
|
|
666
|
+
wrap.appendChild(inp);
|
|
667
|
+
fieldsContainer.appendChild(wrap);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
// Reset manual token fields
|
|
671
|
+
document.getElementById('oauth-field-accessToken').value = '';
|
|
672
|
+
document.getElementById('oauth-field-accessToken').placeholder = detail.connected ? '(token saved — leave blank to keep)' : 'Paste token from Graph API Explorer';
|
|
673
|
+
document.getElementById('oauth-field-userId').value = '';
|
|
674
|
+
document.getElementById('oauth-field-userId').placeholder = detail.connected ? (detail.accountName || 'Instagram Business Account ID') : 'Instagram Business Account ID';
|
|
675
|
+
|
|
676
|
+
// Default to manual mode
|
|
677
|
+
setOAuthMode('manual');
|
|
678
|
+
|
|
679
|
+
// Show connected status
|
|
680
|
+
var statusEl = document.getElementById('connectorOAuthStatus');
|
|
681
|
+
var disconnectBtn = document.getElementById('connectorDisconnectBtn');
|
|
682
|
+
var modeToggle = document.getElementById('connectorOAuthModeToggle');
|
|
683
|
+
if (detail.connected) {
|
|
684
|
+
var displayName = detail.accountName || detail.name;
|
|
685
|
+
statusEl.innerHTML = '<span style="color:var(--themePrimary);">Connected as <strong>' + displayName + '</strong></span>';
|
|
686
|
+
statusEl.style.display = '';
|
|
687
|
+
disconnectBtn.style.display = '';
|
|
688
|
+
modeToggle.style.display = 'none';
|
|
689
|
+
} else {
|
|
690
|
+
statusEl.innerHTML = '';
|
|
691
|
+
statusEl.style.display = 'none';
|
|
692
|
+
disconnectBtn.style.display = 'none';
|
|
693
|
+
modeToggle.style.display = 'flex';
|
|
694
|
+
}
|
|
695
|
+
} else {
|
|
696
|
+
document.getElementById('connectorApiKey').value = '';
|
|
697
|
+
document.getElementById('connectorApiKey').placeholder = detail.hasKey ? '(key saved — leave blank to keep)' : 'Enter your API key';
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
document.getElementById('connectorModal').classList.add('flm-dialog-overlay--open');
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
var currentOAuthMode = 'manual';
|
|
705
|
+
|
|
706
|
+
function setOAuthMode(mode) {
|
|
707
|
+
currentOAuthMode = mode;
|
|
708
|
+
document.getElementById('connectorManualGroup').style.display = mode === 'manual' ? '' : 'none';
|
|
709
|
+
document.getElementById('connectorAppGroup').style.display = mode === 'app' ? '' : 'none';
|
|
710
|
+
document.getElementById('oauthModeManual').className = 'flm-pivot-tab' + (mode === 'manual' ? ' flm-pivot-tab--active' : '');
|
|
711
|
+
document.getElementById('oauthModeApp').className = 'flm-pivot-tab' + (mode === 'app' ? ' flm-pivot-tab--active' : '');
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
document.getElementById('oauthModeManual').addEventListener('click', function() { setOAuthMode('manual'); });
|
|
715
|
+
document.getElementById('oauthModeApp').addEventListener('click', function() { setOAuthMode('app'); });
|
|
716
|
+
|
|
717
|
+
function closeConnectorModal() {
|
|
718
|
+
document.getElementById('connectorModal').classList.remove('flm-dialog-overlay--open');
|
|
719
|
+
currentConnectorId = null;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
document.getElementById('connectorCancelBtn').addEventListener('click', closeConnectorModal);
|
|
723
|
+
|
|
724
|
+
var connectorModalMouseDownTarget = null;
|
|
725
|
+
document.getElementById('connectorModal').addEventListener('mousedown', function(e) { connectorModalMouseDownTarget = e.target; });
|
|
726
|
+
document.getElementById('connectorModal').addEventListener('click', function(e) {
|
|
727
|
+
if (e.target === this && connectorModalMouseDownTarget === this) closeConnectorModal();
|
|
728
|
+
connectorModalMouseDownTarget = null;
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
document.getElementById('connectorSaveBtn').addEventListener('click', function() {
|
|
732
|
+
if (!currentConnectorId) return;
|
|
733
|
+
var isOAuth = currentConnectorDetail && currentConnectorDetail.authStrategy === 'oauth2';
|
|
734
|
+
var body;
|
|
735
|
+
if (isOAuth) {
|
|
736
|
+
body = { enabled: document.getElementById('connectorEnabled').checked };
|
|
737
|
+
if (currentOAuthMode === 'manual') {
|
|
738
|
+
// Manual token entry
|
|
739
|
+
var token = document.getElementById('oauth-field-accessToken').value;
|
|
740
|
+
if (token) body.accessToken = token;
|
|
741
|
+
var uid = document.getElementById('oauth-field-userId').value;
|
|
742
|
+
if (uid) body.userId = uid;
|
|
743
|
+
} else {
|
|
744
|
+
// OAuth app credentials
|
|
745
|
+
(currentConnectorDetail.fields || []).forEach(function(f) {
|
|
746
|
+
var el = document.getElementById('oauth-field-' + f.name);
|
|
747
|
+
if (el && el.value) body[f.name] = el.value;
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
} else {
|
|
751
|
+
body = {
|
|
752
|
+
apiKey: document.getElementById('connectorApiKey').value,
|
|
753
|
+
enabled: document.getElementById('connectorEnabled').checked
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
fetch('/api/connectors/' + encodeURIComponent(currentConnectorId), {
|
|
757
|
+
method: 'POST',
|
|
758
|
+
headers: { 'Content-Type': 'application/json' },
|
|
759
|
+
body: JSON.stringify(body)
|
|
760
|
+
}).then(function(r) {
|
|
761
|
+
if (r.ok) { closeConnectorModal(); loadConnectors(); }
|
|
762
|
+
});
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
document.getElementById('connectorConnectBtn').addEventListener('click', function() {
|
|
766
|
+
if (!currentConnectorId || !currentConnectorDetail) return;
|
|
767
|
+
// Save credentials first, then redirect to authorize
|
|
768
|
+
var body = { enabled: true };
|
|
769
|
+
(currentConnectorDetail.fields || []).forEach(function(f) {
|
|
770
|
+
var el = document.getElementById('oauth-field-' + f.name);
|
|
771
|
+
if (el && el.value) body[f.name] = el.value;
|
|
772
|
+
});
|
|
773
|
+
fetch('/api/connectors/' + encodeURIComponent(currentConnectorId), {
|
|
774
|
+
method: 'POST',
|
|
775
|
+
headers: { 'Content-Type': 'application/json' },
|
|
776
|
+
body: JSON.stringify(body)
|
|
777
|
+
}).then(function(r) {
|
|
778
|
+
if (r.ok) {
|
|
779
|
+
window.location.href = '/api/connectors/' + encodeURIComponent(currentConnectorId) + '/authorize';
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
document.getElementById('connectorDisconnectBtn').addEventListener('click', function() {
|
|
785
|
+
if (!currentConnectorId) return;
|
|
786
|
+
fetch('/api/connectors/' + encodeURIComponent(currentConnectorId), { method: 'DELETE' }).then(function(r) {
|
|
787
|
+
if (r.ok) { closeConnectorModal(); loadConnectors(); }
|
|
788
|
+
});
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
document.getElementById('connectorRemoveBtn').addEventListener('click', function() {
|
|
792
|
+
if (!currentConnectorId) return;
|
|
793
|
+
fetch('/api/connectors/' + encodeURIComponent(currentConnectorId), { method: 'DELETE' }).then(function(r) {
|
|
794
|
+
if (r.ok) { closeConnectorModal(); loadConnectors(); }
|
|
795
|
+
});
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
document.getElementById('connectorSearch').addEventListener('input', renderConnectorGrid);
|
|
799
|
+
|
|
800
|
+
// More dropdown toggle
|
|
801
|
+
document.getElementById('connMoreBtn').addEventListener('click', function(e) {
|
|
802
|
+
e.stopPropagation();
|
|
803
|
+
document.getElementById('connMoreMenu').classList.toggle('flm-contextmenu--visible');
|
|
804
|
+
});
|
|
805
|
+
document.addEventListener('click', function() {
|
|
806
|
+
document.getElementById('connMoreMenu').classList.remove('flm-contextmenu--visible');
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// Resize observer — recalculate visible categories when filter bar resizes
|
|
810
|
+
var connFilterBar = document.getElementById('connectorFilterBar');
|
|
811
|
+
if (connFilterBar && typeof ResizeObserver !== 'undefined') {
|
|
812
|
+
var connResizeObserver = new ResizeObserver(function() {
|
|
813
|
+
if (connectorList.length > 0) renderConnectorFilterBar();
|
|
814
|
+
});
|
|
815
|
+
connResizeObserver.observe(connFilterBar);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Detect OAuth success/error from URL params
|
|
819
|
+
var connectedParam = params.get('connected');
|
|
820
|
+
var errorParam = params.get('error');
|
|
821
|
+
if (connectedParam) {
|
|
822
|
+
// Open the connectors tab and show success
|
|
823
|
+
openSection('connectors');
|
|
824
|
+
setTimeout(function() { openConnectorModal(connectedParam); }, 500);
|
|
825
|
+
// Clean URL
|
|
826
|
+
var cleanUrl = new URL(window.location);
|
|
827
|
+
cleanUrl.searchParams.delete('connected');
|
|
828
|
+
history.replaceState(null, '', cleanUrl);
|
|
829
|
+
}
|
|
830
|
+
if (errorParam) {
|
|
831
|
+
openSection('connectors');
|
|
832
|
+
alert('OAuth error: ' + errorParam);
|
|
833
|
+
var cleanUrl2 = new URL(window.location);
|
|
834
|
+
cleanUrl2.searchParams.delete('error');
|
|
835
|
+
history.replaceState(null, '', cleanUrl2);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// --- Agents logic ---
|
|
839
|
+
var agentList = [], currentAgentId = null, currentAgentSkills = null, currentAgentCapabilities = null;
|
|
840
|
+
var currentAgentType = 'a2a';
|
|
841
|
+
var chatAgentId = null;
|
|
842
|
+
|
|
843
|
+
function loadAgents() {
|
|
844
|
+
fetch('/api/agents').then(function(r) { return r.json(); }).then(function(list) {
|
|
845
|
+
agentList = list;
|
|
846
|
+
renderAgentList();
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function renderAgentList() {
|
|
851
|
+
var listEl = document.getElementById('agentList');
|
|
852
|
+
var emptyState = document.getElementById('agentEmptyState');
|
|
853
|
+
listEl.innerHTML = '';
|
|
854
|
+
listEl.style.display = agentList.length === 0 ? 'none' : '';
|
|
855
|
+
emptyState.style.display = agentList.length === 0 ? '' : 'none';
|
|
856
|
+
|
|
857
|
+
agentList.forEach(function(a) {
|
|
858
|
+
var row = document.createElement('div');
|
|
859
|
+
row.className = 'flm-list-item';
|
|
860
|
+
row.style.cssText = 'display:flex;align-items:center;gap:12px;padding:12px 16px;cursor:pointer;';
|
|
861
|
+
|
|
862
|
+
// Status dot (openclaw only)
|
|
863
|
+
if (a.provider === 'openclaw') {
|
|
864
|
+
var dot = document.createElement('div');
|
|
865
|
+
dot.style.cssText = 'width:8px;height:8px;border-radius:50%;flex-shrink:0;background:' + (a.connected ? 'var(--successText)' : 'var(--disabledText)');
|
|
866
|
+
dot.title = a.connected ? 'Connected' : 'Disconnected';
|
|
867
|
+
row.appendChild(dot);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Info section
|
|
871
|
+
var info = document.createElement('div');
|
|
872
|
+
info.className = 'flm-list-item-content';
|
|
873
|
+
info.style.cssText = 'flex:1;min-width:0;display:flex;align-items:center;gap:10px;';
|
|
874
|
+
|
|
875
|
+
var nameEl = document.createElement('span');
|
|
876
|
+
nameEl.className = 'flm-list-item-primary flm-text--semibold flm-text--nowrap';
|
|
877
|
+
nameEl.textContent = a.name;
|
|
878
|
+
|
|
879
|
+
var descEl = document.createElement('span');
|
|
880
|
+
descEl.className = 'flm-list-item-secondary flm-text--nowrap';
|
|
881
|
+
descEl.style.cssText = 'flex:1;min-width:0;';
|
|
882
|
+
descEl.textContent = a.description || '';
|
|
883
|
+
|
|
884
|
+
info.appendChild(nameEl);
|
|
885
|
+
info.appendChild(descEl);
|
|
886
|
+
row.appendChild(info);
|
|
887
|
+
|
|
888
|
+
// Badge
|
|
889
|
+
var badge = document.createElement('span');
|
|
890
|
+
badge.className = 'flm-text flm-text--small flm-text--secondary';
|
|
891
|
+
badge.style.cssText = 'background:var(--defaultHoverBackground);padding:2px 8px;border-radius:10px;white-space:nowrap;flex-shrink:0;';
|
|
892
|
+
badge.textContent = a.provider === 'openclaw' ? 'OpenClaw' : 'A2A';
|
|
893
|
+
row.appendChild(badge);
|
|
894
|
+
|
|
895
|
+
// Actions
|
|
896
|
+
var actions = document.createElement('div');
|
|
897
|
+
actions.style.cssText = 'display:flex;align-items:center;gap:8px;flex-shrink:0;';
|
|
898
|
+
|
|
899
|
+
// Chat or Reconnect button (depending on connection state)
|
|
900
|
+
var isDisconnected = a.provider === 'openclaw' && a.enabled !== false && !a.connected;
|
|
901
|
+
var chatBtn = document.createElement('button');
|
|
902
|
+
chatBtn.className = 'flm-button flm-button--subtle';
|
|
903
|
+
chatBtn.style.cssText = 'font-size:12px;padding:4px 10px;';
|
|
904
|
+
if (isDisconnected) {
|
|
905
|
+
chatBtn.textContent = 'Reconnect';
|
|
906
|
+
chatBtn.addEventListener('click', (function(agent) {
|
|
907
|
+
return function(e) {
|
|
908
|
+
e.stopPropagation();
|
|
909
|
+
chatBtn.disabled = true;
|
|
910
|
+
chatBtn.textContent = 'Connecting...';
|
|
911
|
+
fetch('/api/agents/' + encodeURIComponent(agent.id) + '/connect', { method: 'POST' })
|
|
912
|
+
.then(function() { loadAgents(); })
|
|
913
|
+
.catch(function() { chatBtn.disabled = false; chatBtn.textContent = 'Reconnect'; });
|
|
914
|
+
};
|
|
915
|
+
})(a));
|
|
916
|
+
} else {
|
|
917
|
+
chatBtn.textContent = 'Chat';
|
|
918
|
+
chatBtn.addEventListener('click', function(e) {
|
|
919
|
+
e.stopPropagation();
|
|
920
|
+
openAgentChat(a);
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
actions.appendChild(chatBtn);
|
|
924
|
+
|
|
925
|
+
// Edit button
|
|
926
|
+
var editBtn = document.createElement('button');
|
|
927
|
+
editBtn.className = 'flm-button flm-button--subtle';
|
|
928
|
+
editBtn.textContent = 'Edit';
|
|
929
|
+
editBtn.style.cssText = 'font-size:12px;padding:4px 10px;';
|
|
930
|
+
editBtn.addEventListener('click', function(e) {
|
|
931
|
+
e.stopPropagation();
|
|
932
|
+
openAgentEditModal(a);
|
|
933
|
+
});
|
|
934
|
+
actions.appendChild(editBtn);
|
|
935
|
+
|
|
936
|
+
// Enable/disable toggle
|
|
937
|
+
var toggleLabel = document.createElement('label');
|
|
938
|
+
toggleLabel.className = 'flm-toggle flm-toggle--inline';
|
|
939
|
+
var toggleInput = document.createElement('input');
|
|
940
|
+
toggleInput.type = 'checkbox';
|
|
941
|
+
toggleInput.className = 'flm-toggle-input';
|
|
942
|
+
toggleInput.checked = a.enabled !== false;
|
|
943
|
+
toggleInput.addEventListener('click', function(e) { e.stopPropagation(); });
|
|
944
|
+
toggleInput.addEventListener('change', (function(agentId, inp) {
|
|
945
|
+
return function() {
|
|
946
|
+
fetch('/api/agents/' + encodeURIComponent(agentId), {
|
|
947
|
+
method: 'PATCH',
|
|
948
|
+
headers: { 'Content-Type': 'application/json' },
|
|
949
|
+
body: JSON.stringify({ enabled: inp.checked })
|
|
950
|
+
}).then(function() { loadAgents(); });
|
|
951
|
+
};
|
|
952
|
+
})(a.id, toggleInput));
|
|
953
|
+
var toggleTrack = document.createElement('span');
|
|
954
|
+
toggleTrack.className = 'flm-toggle-track';
|
|
955
|
+
var toggleThumb = document.createElement('span');
|
|
956
|
+
toggleThumb.className = 'flm-toggle-thumb';
|
|
957
|
+
toggleTrack.appendChild(toggleThumb);
|
|
958
|
+
toggleLabel.appendChild(toggleInput);
|
|
959
|
+
toggleLabel.appendChild(toggleTrack);
|
|
960
|
+
actions.appendChild(toggleLabel);
|
|
961
|
+
|
|
962
|
+
row.appendChild(actions);
|
|
963
|
+
listEl.appendChild(row);
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// --- Agent quick chat ---
|
|
968
|
+
function openAgentChat(agent) {
|
|
969
|
+
chatAgentId = agent.id;
|
|
970
|
+
document.getElementById('agentChatTitle').textContent = 'Chat with ' + agent.name;
|
|
971
|
+
document.getElementById('agentChatMessages').innerHTML = '';
|
|
972
|
+
document.getElementById('agentChatInput').value = '';
|
|
973
|
+
var panel = document.getElementById('agentChatPanel');
|
|
974
|
+
panel.classList.add('open');
|
|
975
|
+
document.getElementById('agentChatInput').focus();
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function closeAgentChat() {
|
|
979
|
+
chatAgentId = null;
|
|
980
|
+
document.getElementById('agentChatPanel').classList.remove('open');
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function addChatMsg(role, text) {
|
|
984
|
+
var msgs = document.getElementById('agentChatMessages');
|
|
985
|
+
var div = document.createElement('div');
|
|
986
|
+
div.className = 'agent-chat-msg ' + role;
|
|
987
|
+
div.textContent = text;
|
|
988
|
+
msgs.appendChild(div);
|
|
989
|
+
msgs.scrollTop = msgs.scrollHeight;
|
|
990
|
+
return div;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
function sendAgentChatMessage() {
|
|
994
|
+
if (!chatAgentId) return;
|
|
995
|
+
var input = document.getElementById('agentChatInput');
|
|
996
|
+
var message = input.value.trim();
|
|
997
|
+
if (!message) return;
|
|
998
|
+
|
|
999
|
+
input.value = '';
|
|
1000
|
+
addChatMsg('user', message);
|
|
1001
|
+
|
|
1002
|
+
var sendBtn = document.getElementById('agentChatSendBtn');
|
|
1003
|
+
sendBtn.disabled = true;
|
|
1004
|
+
sendBtn.textContent = '...';
|
|
1005
|
+
|
|
1006
|
+
// Check if agent supports streaming
|
|
1007
|
+
var agent = agentList.find(function(a) { return a.id === chatAgentId; });
|
|
1008
|
+
var supportsStreaming = agent && agent.capabilities && agent.capabilities.streaming;
|
|
1009
|
+
|
|
1010
|
+
if (supportsStreaming) {
|
|
1011
|
+
// Use streaming
|
|
1012
|
+
var responseDiv = addChatMsg('agent', '');
|
|
1013
|
+
var responseText = '';
|
|
1014
|
+
|
|
1015
|
+
fetch('/api/agents/' + encodeURIComponent(chatAgentId) + '/stream', {
|
|
1016
|
+
method: 'POST',
|
|
1017
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1018
|
+
body: JSON.stringify({ message: message })
|
|
1019
|
+
}).then(function(res) {
|
|
1020
|
+
if (!res.ok) return res.json().then(function(d) { throw new Error(d.error || 'Send failed'); });
|
|
1021
|
+
var reader = res.body.getReader();
|
|
1022
|
+
var decoder = new TextDecoder();
|
|
1023
|
+
var buffer = '';
|
|
1024
|
+
function pump() {
|
|
1025
|
+
return reader.read().then(function(result) {
|
|
1026
|
+
if (result.done) { sendBtn.disabled = false; sendBtn.textContent = 'Send'; return; }
|
|
1027
|
+
buffer += decoder.decode(result.value, { stream: true });
|
|
1028
|
+
var lines = buffer.split('\n');
|
|
1029
|
+
buffer = lines.pop() || '';
|
|
1030
|
+
for (var i = 0; i < lines.length; i++) {
|
|
1031
|
+
var line = lines[i];
|
|
1032
|
+
if (line.indexOf('data: ') === 0) {
|
|
1033
|
+
var data = line.substring(6);
|
|
1034
|
+
if (data === '[DONE]') { sendBtn.disabled = false; sendBtn.textContent = 'Send'; return; }
|
|
1035
|
+
try {
|
|
1036
|
+
var evt = JSON.parse(data);
|
|
1037
|
+
if (evt.kind === 'text' && evt.data) {
|
|
1038
|
+
responseText += evt.data;
|
|
1039
|
+
responseDiv.textContent = responseText;
|
|
1040
|
+
document.getElementById('agentChatMessages').scrollTop = document.getElementById('agentChatMessages').scrollHeight;
|
|
1041
|
+
} else if (evt.kind === 'error') {
|
|
1042
|
+
addChatMsg('error', 'Error: ' + (evt.data || 'Unknown error'));
|
|
1043
|
+
sendBtn.disabled = false; sendBtn.textContent = 'Send';
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
} catch (e) {}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return pump();
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
return pump();
|
|
1053
|
+
}).catch(function(err) {
|
|
1054
|
+
if (!responseText) responseDiv.remove();
|
|
1055
|
+
addChatMsg('error', 'Error: ' + err.message);
|
|
1056
|
+
sendBtn.disabled = false; sendBtn.textContent = 'Send';
|
|
1057
|
+
});
|
|
1058
|
+
} else {
|
|
1059
|
+
// Non-streaming
|
|
1060
|
+
addChatMsg('status', 'Waiting for response...');
|
|
1061
|
+
fetch('/api/agents/' + encodeURIComponent(chatAgentId) + '/send', {
|
|
1062
|
+
method: 'POST',
|
|
1063
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1064
|
+
body: JSON.stringify({ message: message })
|
|
1065
|
+
}).then(function(r) {
|
|
1066
|
+
if (!r.ok) return r.json().then(function(d) { throw new Error(d.error || 'Send failed'); });
|
|
1067
|
+
return r.json();
|
|
1068
|
+
}).then(function(result) {
|
|
1069
|
+
// Remove "waiting" status
|
|
1070
|
+
var msgs = document.getElementById('agentChatMessages');
|
|
1071
|
+
var last = msgs.lastElementChild;
|
|
1072
|
+
if (last && last.classList.contains('status')) last.remove();
|
|
1073
|
+
addChatMsg('agent', result.text || JSON.stringify(result.raw, null, 2));
|
|
1074
|
+
}).catch(function(err) {
|
|
1075
|
+
var msgs = document.getElementById('agentChatMessages');
|
|
1076
|
+
var last = msgs.lastElementChild;
|
|
1077
|
+
if (last && last.classList.contains('status')) last.remove();
|
|
1078
|
+
addChatMsg('error', 'Error: ' + err.message);
|
|
1079
|
+
}).finally(function() {
|
|
1080
|
+
sendBtn.disabled = false; sendBtn.textContent = 'Send';
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
document.getElementById('agentChatClose').addEventListener('click', closeAgentChat);
|
|
1086
|
+
document.getElementById('agentChatSendBtn').addEventListener('click', sendAgentChatMessage);
|
|
1087
|
+
document.getElementById('agentChatInput').addEventListener('keydown', function(e) {
|
|
1088
|
+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendAgentChatMessage(); }
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
function setAgentType(type) {
|
|
1092
|
+
currentAgentType = type;
|
|
1093
|
+
document.getElementById('agentTypeA2A').className = 'flm-pivot-tab' + (type === 'a2a' ? ' flm-pivot-tab--active' : '');
|
|
1094
|
+
document.getElementById('agentTypeOpenClaw').className = 'flm-pivot-tab' + (type === 'openclaw' ? ' flm-pivot-tab--active' : '');
|
|
1095
|
+
document.getElementById('agentTokenGroup').style.display = type === 'openclaw' ? '' : 'none';
|
|
1096
|
+
document.getElementById('agentSessionKeyGroup').style.display = type === 'openclaw' ? '' : 'none';
|
|
1097
|
+
document.getElementById('agentSshTunnelGroup').style.display = type === 'openclaw' ? '' : 'none';
|
|
1098
|
+
document.getElementById('agentUrlHint').textContent = type === 'openclaw'
|
|
1099
|
+
? 'Enter the gateway HTTP URL and token, then click Discover to verify connectivity.'
|
|
1100
|
+
: 'Enter a URL and click Discover to auto-fill from the agent card, or fill in the fields manually.';
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function resetAgentModal() {
|
|
1104
|
+
currentAgentId = null;
|
|
1105
|
+
currentAgentSkills = null;
|
|
1106
|
+
currentAgentCapabilities = null;
|
|
1107
|
+
setAgentType('a2a');
|
|
1108
|
+
document.getElementById('agentUrl').value = '';
|
|
1109
|
+
document.getElementById('agentToken').value = '';
|
|
1110
|
+
document.getElementById('agentSessionKey').value = '';
|
|
1111
|
+
document.getElementById('agentName').value = '';
|
|
1112
|
+
document.getElementById('agentDescription').value = '';
|
|
1113
|
+
document.getElementById('agentSkillsPreview').style.display = 'none';
|
|
1114
|
+
document.getElementById('agentSkillsList').innerHTML = '';
|
|
1115
|
+
document.getElementById('agentRemoveBtn').style.display = 'none';
|
|
1116
|
+
// Reset SSH tunnel fields
|
|
1117
|
+
document.getElementById('agentSshTunnelEnabled').checked = false;
|
|
1118
|
+
document.getElementById('agentSshCommand').value = '';
|
|
1119
|
+
document.getElementById('agentSshPassword').value = '';
|
|
1120
|
+
document.getElementById('agentSshTunnelFields').style.display = 'none';
|
|
1121
|
+
document.getElementById('agentSshTunnelArrow').innerHTML = '▶';
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
function openAgentAddModal() {
|
|
1125
|
+
resetAgentModal();
|
|
1126
|
+
document.getElementById('agentModalTitle').textContent = 'Add Agent';
|
|
1127
|
+
document.getElementById('agentModal').classList.add('flm-dialog-overlay--open');
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function openAgentEditModal(agent) {
|
|
1131
|
+
resetAgentModal();
|
|
1132
|
+
currentAgentId = agent.id;
|
|
1133
|
+
setAgentType(agent.provider || 'a2a');
|
|
1134
|
+
document.getElementById('agentModalTitle').textContent = 'Edit Agent';
|
|
1135
|
+
document.getElementById('agentUrl').value = agent.url || '';
|
|
1136
|
+
document.getElementById('agentName').value = agent.name || '';
|
|
1137
|
+
document.getElementById('agentDescription').value = agent.description || '';
|
|
1138
|
+
// Token is stripped by the server — show placeholder in edit mode for openclaw
|
|
1139
|
+
if (agent.provider === 'openclaw') {
|
|
1140
|
+
document.getElementById('agentToken').placeholder = '(token saved — leave blank to keep)';
|
|
1141
|
+
document.getElementById('agentSessionKey').value = agent.sessionKey || '';
|
|
1142
|
+
// Populate SSH tunnel fields (password is stripped by server)
|
|
1143
|
+
if (agent.sshTunnel) {
|
|
1144
|
+
document.getElementById('agentSshTunnelEnabled').checked = !!agent.sshTunnel.enabled;
|
|
1145
|
+
document.getElementById('agentSshCommand').value = agent.sshTunnel.command || '';
|
|
1146
|
+
document.getElementById('agentSshPassword').placeholder = '(password saved — leave blank to keep)';
|
|
1147
|
+
if (agent.sshTunnel.enabled) {
|
|
1148
|
+
document.getElementById('agentSshTunnelFields').style.display = '';
|
|
1149
|
+
document.getElementById('agentSshTunnelArrow').innerHTML = '▼';
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
document.getElementById('agentRemoveBtn').style.display = '';
|
|
1154
|
+
document.getElementById('agentModal').classList.add('flm-dialog-overlay--open');
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function closeAgentModal() {
|
|
1158
|
+
document.getElementById('agentModal').classList.remove('flm-dialog-overlay--open');
|
|
1159
|
+
resetAgentModal();
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function renderSkillsList(skills) {
|
|
1163
|
+
var container = document.getElementById('agentSkillsList');
|
|
1164
|
+
var wrapper = document.getElementById('agentSkillsPreview');
|
|
1165
|
+
if (!skills || skills.length === 0) {
|
|
1166
|
+
wrapper.style.display = 'none';
|
|
1167
|
+
container.innerHTML = '';
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
wrapper.style.display = '';
|
|
1171
|
+
container.innerHTML = skills.map(function(s) {
|
|
1172
|
+
var label = s.name || s.id || 'Skill';
|
|
1173
|
+
var desc = s.description ? ' — ' + s.description : '';
|
|
1174
|
+
return '<span style="display:inline-block;padding:2px 8px;margin:2px;border-radius:8px;background:var(--defaultHoverBackground);font-size:11px;">' + label + desc + '</span>';
|
|
1175
|
+
}).join('');
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
document.getElementById('agentTypeA2A').addEventListener('click', function() { setAgentType('a2a'); });
|
|
1179
|
+
document.getElementById('agentTypeOpenClaw').addEventListener('click', function() { setAgentType('openclaw'); });
|
|
1180
|
+
|
|
1181
|
+
// SSH Tunnel collapsible section
|
|
1182
|
+
document.getElementById('agentSshTunnelToggleHeader').addEventListener('click', function() {
|
|
1183
|
+
var fields = document.getElementById('agentSshTunnelFields');
|
|
1184
|
+
var arrow = document.getElementById('agentSshTunnelArrow');
|
|
1185
|
+
var isOpen = fields.style.display !== 'none';
|
|
1186
|
+
fields.style.display = isOpen ? 'none' : '';
|
|
1187
|
+
arrow.innerHTML = isOpen ? '▶' : '▼';
|
|
1188
|
+
});
|
|
1189
|
+
document.getElementById('agentSshTunnelEnabled').addEventListener('change', function() {
|
|
1190
|
+
if (this.checked) {
|
|
1191
|
+
document.getElementById('agentSshTunnelFields').style.display = '';
|
|
1192
|
+
document.getElementById('agentSshTunnelArrow').innerHTML = '▼';
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
document.getElementById('addAgentBtn').addEventListener('click', function(e) {
|
|
1197
|
+
e.stopPropagation();
|
|
1198
|
+
openAgentAddModal();
|
|
1199
|
+
});
|
|
1200
|
+
document.getElementById('agentCancelBtn').addEventListener('click', closeAgentModal);
|
|
1201
|
+
|
|
1202
|
+
var agentModalMouseDownTarget = null;
|
|
1203
|
+
document.getElementById('agentModal').addEventListener('mousedown', function(e) { agentModalMouseDownTarget = e.target; });
|
|
1204
|
+
document.getElementById('agentModal').addEventListener('click', function(e) {
|
|
1205
|
+
if (e.target === this && agentModalMouseDownTarget === this) closeAgentModal();
|
|
1206
|
+
agentModalMouseDownTarget = null;
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
document.getElementById('agentDiscoverBtn').addEventListener('click', function() {
|
|
1210
|
+
var url = document.getElementById('agentUrl').value.trim();
|
|
1211
|
+
if (!url) return;
|
|
1212
|
+
var btn = this;
|
|
1213
|
+
btn.disabled = true;
|
|
1214
|
+
btn.textContent = 'Discovering...';
|
|
1215
|
+
|
|
1216
|
+
var body = { url: url, type: currentAgentType };
|
|
1217
|
+
if (currentAgentType === 'openclaw') {
|
|
1218
|
+
var token = document.getElementById('agentToken').value.trim();
|
|
1219
|
+
if (!token) { alert('Token is required for OpenClaw discovery.'); btn.disabled = false; btn.textContent = 'Discover'; return; }
|
|
1220
|
+
body.token = token;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
fetch('/api/agents/discover', {
|
|
1224
|
+
method: 'POST',
|
|
1225
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1226
|
+
body: JSON.stringify(body)
|
|
1227
|
+
}).then(function(r) {
|
|
1228
|
+
if (!r.ok) return r.json().then(function(d) { throw new Error(d.error || 'Discovery failed'); });
|
|
1229
|
+
return r.json();
|
|
1230
|
+
}).then(function(result) {
|
|
1231
|
+
var nameEl = document.getElementById('agentName');
|
|
1232
|
+
var descEl = document.getElementById('agentDescription');
|
|
1233
|
+
if (!nameEl.value.trim() && result.name) nameEl.value = result.name;
|
|
1234
|
+
if (!descEl.value.trim() && result.description) descEl.value = result.description;
|
|
1235
|
+
if (result.skills) {
|
|
1236
|
+
currentAgentSkills = result.skills;
|
|
1237
|
+
renderSkillsList(currentAgentSkills);
|
|
1238
|
+
}
|
|
1239
|
+
if (result.capabilities) {
|
|
1240
|
+
currentAgentCapabilities = result.capabilities;
|
|
1241
|
+
}
|
|
1242
|
+
if (currentAgentType === 'openclaw' && result.verified) {
|
|
1243
|
+
alert('Gateway verified successfully!');
|
|
1244
|
+
}
|
|
1245
|
+
}).catch(function(err) {
|
|
1246
|
+
alert('Discovery failed: ' + err.message);
|
|
1247
|
+
}).finally(function() {
|
|
1248
|
+
btn.disabled = false;
|
|
1249
|
+
btn.textContent = 'Discover';
|
|
1250
|
+
});
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
document.getElementById('agentSaveBtn').addEventListener('click', function() {
|
|
1254
|
+
var name = document.getElementById('agentName').value.trim();
|
|
1255
|
+
var url = document.getElementById('agentUrl').value.trim();
|
|
1256
|
+
var description = document.getElementById('agentDescription').value.trim();
|
|
1257
|
+
if (!name) { alert('Name is required.'); return; }
|
|
1258
|
+
if (!url) { alert('Agent URL is required.'); return; }
|
|
1259
|
+
if (!description) { alert('Description is required — explain what this agent does so pages know when to use it.'); return; }
|
|
1260
|
+
|
|
1261
|
+
var body = {
|
|
1262
|
+
url: url,
|
|
1263
|
+
name: name,
|
|
1264
|
+
description: description,
|
|
1265
|
+
enabled: true,
|
|
1266
|
+
provider: currentAgentType
|
|
1267
|
+
};
|
|
1268
|
+
if (currentAgentId) body.id = currentAgentId;
|
|
1269
|
+
if (currentAgentSkills) body.skills = currentAgentSkills;
|
|
1270
|
+
if (currentAgentCapabilities) body.capabilities = currentAgentCapabilities;
|
|
1271
|
+
|
|
1272
|
+
// Include token for openclaw agents
|
|
1273
|
+
if (currentAgentType === 'openclaw') {
|
|
1274
|
+
var token = document.getElementById('agentToken').value.trim();
|
|
1275
|
+
if (token) body.token = token;
|
|
1276
|
+
// For new openclaw agents, token is required
|
|
1277
|
+
if (!token && !currentAgentId) { alert('Token is required for OpenClaw agents.'); return; }
|
|
1278
|
+
// Set streaming capability by default for openclaw
|
|
1279
|
+
if (!body.capabilities) body.capabilities = { streaming: true };
|
|
1280
|
+
var sessionKey = document.getElementById('agentSessionKey').value.trim();
|
|
1281
|
+
if (sessionKey) body.sessionKey = sessionKey;
|
|
1282
|
+
|
|
1283
|
+
// SSH tunnel config
|
|
1284
|
+
var sshEnabled = document.getElementById('agentSshTunnelEnabled').checked;
|
|
1285
|
+
var sshCommand = document.getElementById('agentSshCommand').value.trim();
|
|
1286
|
+
var sshPassword = document.getElementById('agentSshPassword').value;
|
|
1287
|
+
if (sshEnabled || sshCommand) {
|
|
1288
|
+
body.sshTunnel = {
|
|
1289
|
+
enabled: sshEnabled,
|
|
1290
|
+
command: sshCommand
|
|
1291
|
+
};
|
|
1292
|
+
// Only include password if provided (otherwise server preserves existing)
|
|
1293
|
+
if (sshPassword) body.sshTunnel.password = sshPassword;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
fetch('/api/agents', {
|
|
1298
|
+
method: 'POST',
|
|
1299
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1300
|
+
body: JSON.stringify(body)
|
|
1301
|
+
}).then(function(r) {
|
|
1302
|
+
if (r.ok) { closeAgentModal(); loadAgents(); }
|
|
1303
|
+
});
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
document.getElementById('agentRemoveBtn').addEventListener('click', function() {
|
|
1307
|
+
if (!currentAgentId) return;
|
|
1308
|
+
fetch('/api/agents/' + encodeURIComponent(currentAgentId), { method: 'DELETE' }).then(function(r) {
|
|
1309
|
+
if (r.ok) { closeAgentModal(); loadAgents(); }
|
|
1310
|
+
});
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
// --- Save logic ---
|
|
1314
|
+
function saveAllSettings() {
|
|
1315
|
+
var builderApiKey = document.getElementById('serviceApiKey-builder').value;
|
|
1316
|
+
var builderModel = document.getElementById('model-builder').value;
|
|
1317
|
+
var builderMaxTokens = document.getElementById('maxTokens-builder').value;
|
|
1318
|
+
var builderProvider = document.getElementById('provider-builder').value;
|
|
1319
|
+
|
|
1320
|
+
if (!builderProvider || !builderApiKey || !builderModel || !builderMaxTokens) {
|
|
1321
|
+
alert('Please fill in all required Page Builder fields (Provider, API Key, Model, Max Output Tokens).');
|
|
1322
|
+
openSection('models');
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
var models = [
|
|
1327
|
+
{
|
|
1328
|
+
use: 'builder',
|
|
1329
|
+
provider: builderProvider,
|
|
1330
|
+
configuration: {
|
|
1331
|
+
apiKey: builderApiKey,
|
|
1332
|
+
model: builderModel,
|
|
1333
|
+
maxTokens: builderMaxTokens
|
|
1334
|
+
},
|
|
1335
|
+
imageQuality: 'standard',
|
|
1336
|
+
instructions: document.getElementById('instructions-builder').value
|
|
1337
|
+
},
|
|
1338
|
+
{
|
|
1339
|
+
use: 'chat',
|
|
1340
|
+
provider: document.getElementById('provider-chat').value,
|
|
1341
|
+
configuration: {
|
|
1342
|
+
apiKey: document.getElementById('serviceApiKey-chat').value,
|
|
1343
|
+
model: document.getElementById('model-chat').value,
|
|
1344
|
+
maxTokens: document.getElementById('maxTokens-chat').value
|
|
1345
|
+
},
|
|
1346
|
+
imageQuality: 'standard',
|
|
1347
|
+
instructions: document.getElementById('instructions-chat').value
|
|
1348
|
+
}
|
|
1349
|
+
];
|
|
1350
|
+
|
|
1351
|
+
var body = {
|
|
1352
|
+
version: 2,
|
|
1353
|
+
theme: document.getElementById('theme').value,
|
|
1354
|
+
toolbarPosition: document.getElementById('toolbarPosition').value,
|
|
1355
|
+
models: models,
|
|
1356
|
+
features: []
|
|
1357
|
+
};
|
|
1358
|
+
|
|
1359
|
+
fetch('/api/settings', {
|
|
1360
|
+
method: 'POST',
|
|
1361
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1362
|
+
body: JSON.stringify(body)
|
|
1363
|
+
}).then(function(response) {
|
|
1364
|
+
if (response.ok || response.redirected) {
|
|
1365
|
+
settingsDirty = false;
|
|
1366
|
+
if (wizardActive) {
|
|
1367
|
+
window.location.href = '/builder?firstRun=true';
|
|
1368
|
+
} else {
|
|
1369
|
+
window.location.reload();
|
|
1370
|
+
}
|
|
1371
|
+
} else {
|
|
1372
|
+
alert('Failed to save settings. Please try again.');
|
|
1373
|
+
}
|
|
1374
|
+
}).catch(function(err) {
|
|
1375
|
+
console.error('Error saving settings:', err);
|
|
1376
|
+
alert('Failed to save settings. Please try again.');
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
var settingsDirty = false;
|
|
1381
|
+
var applyBtn = document.getElementById('applyBtn');
|
|
1382
|
+
|
|
1383
|
+
function markDirty() {
|
|
1384
|
+
settingsDirty = true;
|
|
1385
|
+
applyBtn.classList.add('flm-button--primary');
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
applyBtn.addEventListener('click', saveAllSettings);
|
|
1389
|
+
|
|
1390
|
+
// --- Unsaved-changes confirmation dialog ---
|
|
1391
|
+
var unsavedOverlay = document.getElementById('unsavedDialog');
|
|
1392
|
+
var pendingNavUrl = null;
|
|
1393
|
+
|
|
1394
|
+
function showUnsavedDialog(url) {
|
|
1395
|
+
pendingNavUrl = url;
|
|
1396
|
+
unsavedOverlay.classList.add('flm-dialog-overlay--open');
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
function hideUnsavedDialog() {
|
|
1400
|
+
unsavedOverlay.classList.remove('flm-dialog-overlay--open');
|
|
1401
|
+
pendingNavUrl = null;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
document.getElementById('unsavedStayBtn').addEventListener('click', hideUnsavedDialog);
|
|
1405
|
+
document.getElementById('unsavedLeaveBtn').addEventListener('click', function() {
|
|
1406
|
+
var url = pendingNavUrl; // capture before hideUnsavedDialog nulls it
|
|
1407
|
+
settingsDirty = false; // disarm beforeunload
|
|
1408
|
+
hideUnsavedDialog();
|
|
1409
|
+
if (url) window.location.href = url;
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
// Light-dismiss (click overlay backdrop)
|
|
1413
|
+
var unsavedMouseDownTarget = null;
|
|
1414
|
+
unsavedOverlay.addEventListener('mousedown', function(e) { unsavedMouseDownTarget = e.target; });
|
|
1415
|
+
unsavedOverlay.addEventListener('click', function(e) {
|
|
1416
|
+
if (e.target === this && unsavedMouseDownTarget === this) hideUnsavedDialog();
|
|
1417
|
+
unsavedMouseDownTarget = null;
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
// Guard in-page navigation links
|
|
1421
|
+
function guardNavigation(el, url) {
|
|
1422
|
+
el.addEventListener('click', function(e) {
|
|
1423
|
+
if (!settingsDirty) return; // clean — let default behavior through
|
|
1424
|
+
e.preventDefault();
|
|
1425
|
+
e.stopImmediatePropagation(); // block page-script handlers from navigating
|
|
1426
|
+
showUnsavedDialog(url);
|
|
1427
|
+
}, true); // capture phase — run before other handlers
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
guardNavigation(document.getElementById('pagesLink'), '/pages');
|
|
1431
|
+
guardNavigation(document.getElementById('pagesBtn'), '/pages');
|
|
1432
|
+
|
|
1433
|
+
// Fallback for browser close / address-bar navigation only.
|
|
1434
|
+
// Skip when the FluentLM dialog is already handling the confirmation.
|
|
1435
|
+
window.addEventListener('beforeunload', function(e) {
|
|
1436
|
+
if (settingsDirty && !unsavedOverlay.classList.contains('flm-dialog-overlay--open')) {
|
|
1437
|
+
e.preventDefault();
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1440
|
+
|
|
1441
|
+
// --- Provider helpers ---
|
|
1442
|
+
var providersData = [];
|
|
1443
|
+
|
|
1444
|
+
function populateProviderDropdowns() {
|
|
1445
|
+
var providerOptions = providersData.map(function(p) {
|
|
1446
|
+
return '<option value="' + p.name + '">' + p.name + '</option>';
|
|
1447
|
+
}).join('');
|
|
1448
|
+
document.getElementById('provider-builder').innerHTML = providerOptions;
|
|
1449
|
+
document.getElementById('provider-chat').innerHTML = providerOptions;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
function populateModelDropdown(use, providerName) {
|
|
1453
|
+
var provider = providersData.find(function(p) { return p.name === providerName; });
|
|
1454
|
+
if (!provider) return;
|
|
1455
|
+
var models = use === 'builder' ? provider.builderModels : provider.chatModels;
|
|
1456
|
+
var options = models.map(function(m) {
|
|
1457
|
+
return '<option value="' + m + '">' + m + '</option>';
|
|
1458
|
+
}).join('');
|
|
1459
|
+
document.getElementById('model-' + use).innerHTML = options;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
document.getElementById('provider-builder').addEventListener('change', function() {
|
|
1463
|
+
populateModelDropdown('builder', this.value);
|
|
1464
|
+
});
|
|
1465
|
+
document.getElementById('provider-chat').addEventListener('change', function() {
|
|
1466
|
+
populateModelDropdown('chat', this.value);
|
|
1467
|
+
});
|
|
1468
|
+
|
|
1469
|
+
// --- Provider signup instructions ---
|
|
1470
|
+
var providerInstructions = {
|
|
1471
|
+
'Anthropic': 'Sign up at <a href="https://platform.claude.com" target="_blank">platform.claude.com</a>, then get your key at <a href="https://console.anthropic.com/settings/keys" target="_blank">console.anthropic.com/settings/keys</a>.',
|
|
1472
|
+
'OpenAI': 'Sign up at <a href="https://auth.openai.com/create-account" target="_blank">auth.openai.com/create-account</a>, then get your key at <a href="https://platform.openai.com/api-keys" target="_blank">platform.openai.com/api-keys</a>.',
|
|
1473
|
+
'FireworksAI': 'Sign up at <a href="https://fireworks.ai" target="_blank">fireworks.ai</a>, then get your key at <a href="https://fireworks.ai/account/api-keys" target="_blank">fireworks.ai/account/api-keys</a>.'
|
|
1474
|
+
};
|
|
1475
|
+
|
|
1476
|
+
// --- "More settings" toggle state (works for all users) ---
|
|
1477
|
+
var builderAdvanced = false;
|
|
1478
|
+
var chatAdvanced = false;
|
|
1479
|
+
|
|
1480
|
+
function applyAdvancedToggle() {
|
|
1481
|
+
document.getElementById('fg-maxTokens-builder').classList.toggle('wizard-hidden', !builderAdvanced);
|
|
1482
|
+
document.getElementById('fg-instructions-builder').classList.toggle('wizard-hidden', !builderAdvanced);
|
|
1483
|
+
document.getElementById('fg-maxTokens-chat').classList.toggle('wizard-hidden', !chatAdvanced);
|
|
1484
|
+
document.getElementById('fg-instructions-chat').classList.toggle('wizard-hidden', !chatAdvanced);
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
document.getElementById('moreSettingsLink').addEventListener('click', function(e) {
|
|
1488
|
+
e.preventDefault();
|
|
1489
|
+
builderAdvanced = !builderAdvanced;
|
|
1490
|
+
this.innerHTML = builderAdvanced ? '▴ Less settings' : '▾ More settings';
|
|
1491
|
+
applyAdvancedToggle();
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
document.getElementById('moreSettingsLinkChat').addEventListener('click', function(e) {
|
|
1495
|
+
e.preventDefault();
|
|
1496
|
+
chatAdvanced = !chatAdvanced;
|
|
1497
|
+
this.innerHTML = chatAdvanced ? '▴ Less settings' : '▾ More settings';
|
|
1498
|
+
applyAdvancedToggle();
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
// Hide advanced fields by default on load
|
|
1502
|
+
applyAdvancedToggle();
|
|
1503
|
+
|
|
1504
|
+
// --- Wizard state ---
|
|
1505
|
+
var wizardActive = false;
|
|
1506
|
+
var builderStep = 0; // 0 = provider only, 1 = + API key, 2 = fully configured
|
|
1507
|
+
|
|
1508
|
+
function updateWizardVisibility() {
|
|
1509
|
+
if (!wizardActive) return;
|
|
1510
|
+
var fgApiKey = document.getElementById('fg-apikey-builder');
|
|
1511
|
+
var fgModel = document.getElementById('fg-model-builder');
|
|
1512
|
+
var moreLinkBuilder = document.getElementById('moreSettingsLink');
|
|
1513
|
+
var chatCardEl = document.getElementById('chatCard');
|
|
1514
|
+
var moreLinkChat = document.getElementById('moreSettingsLinkChat');
|
|
1515
|
+
|
|
1516
|
+
// Step 0: only provider visible
|
|
1517
|
+
fgApiKey.classList.toggle('wizard-hidden', builderStep < 1);
|
|
1518
|
+
|
|
1519
|
+
// Model dropdown visible at step 2 (auto-selected)
|
|
1520
|
+
fgModel.classList.toggle('wizard-hidden', builderStep < 2);
|
|
1521
|
+
|
|
1522
|
+
// Builder "More settings" hidden until step 2
|
|
1523
|
+
moreLinkBuilder.style.display = builderStep >= 2 ? 'inline-block' : 'none';
|
|
1524
|
+
if (builderStep < 2) {
|
|
1525
|
+
document.getElementById('fg-maxTokens-builder').classList.add('wizard-hidden');
|
|
1526
|
+
document.getElementById('fg-instructions-builder').classList.add('wizard-hidden');
|
|
1527
|
+
} else {
|
|
1528
|
+
// Let the toggle state drive visibility
|
|
1529
|
+
applyAdvancedToggle();
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// Chat card: before step 2, show only title grayed out
|
|
1533
|
+
if (builderStep < 2) {
|
|
1534
|
+
['fg-provider-chat', 'fg-apikey-chat', 'fg-model-chat', 'fg-maxTokens-chat', 'fg-instructions-chat'].forEach(function(id) {
|
|
1535
|
+
document.getElementById(id).classList.add('wizard-hidden');
|
|
1536
|
+
});
|
|
1537
|
+
moreLinkChat.style.display = 'none';
|
|
1538
|
+
chatCardEl.classList.add('disabled');
|
|
1539
|
+
} else {
|
|
1540
|
+
// Step 2: show chat card populated — provider, key, model visible; advanced behind toggle
|
|
1541
|
+
['fg-provider-chat', 'fg-apikey-chat', 'fg-model-chat'].forEach(function(id) {
|
|
1542
|
+
document.getElementById(id).classList.remove('wizard-hidden');
|
|
1543
|
+
});
|
|
1544
|
+
moreLinkChat.style.display = 'inline-block';
|
|
1545
|
+
applyAdvancedToggle();
|
|
1546
|
+
chatCardEl.classList.remove('disabled');
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
updateApplyButton();
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
function updateApplyButton() {
|
|
1553
|
+
var btn = document.getElementById('applyBtn');
|
|
1554
|
+
if (!wizardActive) {
|
|
1555
|
+
btn.disabled = false;
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
var hasProvider = !!document.getElementById('provider-builder').value;
|
|
1559
|
+
var hasKey = !!document.getElementById('serviceApiKey-builder').value;
|
|
1560
|
+
var hasModel = !!document.getElementById('model-builder').value;
|
|
1561
|
+
var hasTokens = !!document.getElementById('maxTokens-builder').value;
|
|
1562
|
+
var ready = hasProvider && hasKey && hasModel && hasTokens;
|
|
1563
|
+
btn.disabled = !ready;
|
|
1564
|
+
if (ready) btn.classList.add('flm-button--primary');
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
function autoPopulateChatCard() {
|
|
1568
|
+
var providerVal = document.getElementById('provider-builder').value;
|
|
1569
|
+
var apiKeyVal = document.getElementById('serviceApiKey-builder').value;
|
|
1570
|
+
|
|
1571
|
+
document.getElementById('provider-chat').value = providerVal;
|
|
1572
|
+
populateModelDropdown('chat', providerVal);
|
|
1573
|
+
|
|
1574
|
+
var chatModelSelect = document.getElementById('model-chat');
|
|
1575
|
+
if (chatModelSelect.options.length > 0) {
|
|
1576
|
+
chatModelSelect.selectedIndex = 0;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
document.getElementById('serviceApiKey-chat').value = apiKeyVal;
|
|
1580
|
+
document.getElementById('maxTokens-chat').value = '32000';
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// --- Wizard event listeners ---
|
|
1584
|
+
document.getElementById('provider-builder').addEventListener('change', function() {
|
|
1585
|
+
if (!wizardActive) return;
|
|
1586
|
+
var val = this.value;
|
|
1587
|
+
if (!val) return;
|
|
1588
|
+
if (builderStep < 1) builderStep = 1;
|
|
1589
|
+
var instrEl = document.getElementById('providerInstructions');
|
|
1590
|
+
if (providerInstructions[val]) {
|
|
1591
|
+
instrEl.innerHTML = providerInstructions[val];
|
|
1592
|
+
instrEl.style.display = 'block';
|
|
1593
|
+
} else {
|
|
1594
|
+
instrEl.style.display = 'none';
|
|
1595
|
+
}
|
|
1596
|
+
updateWizardVisibility();
|
|
1597
|
+
});
|
|
1598
|
+
|
|
1599
|
+
document.getElementById('serviceApiKey-builder').addEventListener('input', function() {
|
|
1600
|
+
if (!wizardActive) return;
|
|
1601
|
+
if (this.value.length > 0 && builderStep < 2) {
|
|
1602
|
+
builderStep = 2;
|
|
1603
|
+
document.getElementById('providerInstructions').style.display = 'none';
|
|
1604
|
+
document.getElementById('maxTokens-builder').value = '32000';
|
|
1605
|
+
autoPopulateChatCard();
|
|
1606
|
+
updateWizardVisibility();
|
|
1607
|
+
}
|
|
1608
|
+
updateApplyButton();
|
|
1609
|
+
});
|
|
1610
|
+
|
|
1611
|
+
// --- Load data ---
|
|
1612
|
+
var isConfigured = false;
|
|
1613
|
+
|
|
1614
|
+
Promise.all([
|
|
1615
|
+
fetch('/api/settings').then(function(r) { return r.json(); }),
|
|
1616
|
+
fetch('/api/themes').then(function(r) { return r.json(); })
|
|
1617
|
+
]).then(function(results) {
|
|
1618
|
+
var data = results[0], themes = results[1];
|
|
1619
|
+
|
|
1620
|
+
// Store providers data
|
|
1621
|
+
providersData = data.providers || [];
|
|
1622
|
+
|
|
1623
|
+
// Find builder and chat entries from models array
|
|
1624
|
+
var models = data.models || [];
|
|
1625
|
+
var builder = models.find(function(m) { return m.use === 'builder'; }) || {};
|
|
1626
|
+
var chat = models.find(function(m) { return m.use === 'chat'; }) || {};
|
|
1627
|
+
|
|
1628
|
+
var builderConfig = builder.configuration || {};
|
|
1629
|
+
var chatConfig = chat.configuration || {};
|
|
1630
|
+
var builderApiKey = builderConfig.apiKey || '';
|
|
1631
|
+
var builderModel = builderConfig.model || '';
|
|
1632
|
+
var builderMaxTokens = builderConfig.maxTokens || '';
|
|
1633
|
+
isConfigured = builderApiKey && builderModel && builderMaxTokens;
|
|
1634
|
+
|
|
1635
|
+
// Populate provider dropdowns
|
|
1636
|
+
populateProviderDropdowns();
|
|
1637
|
+
|
|
1638
|
+
// Builder fields
|
|
1639
|
+
document.getElementById('provider-builder').value = builder.provider || 'Anthropic';
|
|
1640
|
+
populateModelDropdown('builder', builder.provider || 'Anthropic');
|
|
1641
|
+
document.getElementById('serviceApiKey-builder').value = builderApiKey;
|
|
1642
|
+
document.getElementById('model-builder').value = builderModel;
|
|
1643
|
+
document.getElementById('maxTokens-builder').value = builderMaxTokens;
|
|
1644
|
+
document.getElementById('instructions-builder').value = builder.instructions || '';
|
|
1645
|
+
|
|
1646
|
+
// Chat fields
|
|
1647
|
+
document.getElementById('provider-chat').value = chat.provider || 'Anthropic';
|
|
1648
|
+
populateModelDropdown('chat', chat.provider || 'Anthropic');
|
|
1649
|
+
document.getElementById('serviceApiKey-chat').value = chatConfig.apiKey || '';
|
|
1650
|
+
document.getElementById('model-chat').value = chatConfig.model || '';
|
|
1651
|
+
document.getElementById('maxTokens-chat').value = chatConfig.maxTokens || '';
|
|
1652
|
+
document.getElementById('instructions-chat').value = chat.instructions || '';
|
|
1653
|
+
|
|
1654
|
+
var currentTheme = data.theme || 'nebula-dusk';
|
|
1655
|
+
document.getElementById('theme').innerHTML = themes.map(function(t) {
|
|
1656
|
+
return '<option value="' + t + '">' + t + '</option>';
|
|
1657
|
+
}).join('');
|
|
1658
|
+
document.getElementById('theme').value = currentTheme;
|
|
1659
|
+
document.getElementById('toolbarPosition').value = data.toolbarPosition || 'left';
|
|
1660
|
+
|
|
1661
|
+
loadConnectors();
|
|
1662
|
+
loadAgents();
|
|
1663
|
+
|
|
1664
|
+
// Attach dirty-tracking listeners to all settings controls
|
|
1665
|
+
['theme', 'toolbarPosition', 'provider-builder', 'serviceApiKey-builder', 'model-builder',
|
|
1666
|
+
'maxTokens-builder', 'instructions-builder', 'provider-chat',
|
|
1667
|
+
'serviceApiKey-chat', 'model-chat', 'maxTokens-chat', 'instructions-chat'
|
|
1668
|
+
].forEach(function(id) {
|
|
1669
|
+
var el = document.getElementById(id);
|
|
1670
|
+
if (el) {
|
|
1671
|
+
el.addEventListener('input', markDirty);
|
|
1672
|
+
el.addEventListener('change', markDirty);
|
|
1673
|
+
}
|
|
1674
|
+
});
|
|
1675
|
+
|
|
1676
|
+
var isFirstRun = params.get('firstRun') === '1';
|
|
1677
|
+
|
|
1678
|
+
if (!isConfigured) {
|
|
1679
|
+
// Disable chat and navigation
|
|
1680
|
+
document.getElementById('chatInput').disabled = true;
|
|
1681
|
+
document.getElementById('chatInput').classList.add('disabled');
|
|
1682
|
+
var pagesLink = document.getElementById('pagesLink');
|
|
1683
|
+
pagesLink.style.opacity = '0.4';
|
|
1684
|
+
pagesLink.style.pointerEvents = 'none';
|
|
1685
|
+
|
|
1686
|
+
// Show config required banner and open models section
|
|
1687
|
+
document.getElementById('configBanner').style.display = 'block';
|
|
1688
|
+
openSection('models');
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
if (isFirstRun) {
|
|
1692
|
+
// Activate wizard — always treat as unconfigured when firstRun=1
|
|
1693
|
+
wizardActive = true;
|
|
1694
|
+
builderStep = 0;
|
|
1695
|
+
|
|
1696
|
+
// Disable chat and navigation (even if somehow configured)
|
|
1697
|
+
document.getElementById('chatInput').disabled = true;
|
|
1698
|
+
document.getElementById('chatInput').classList.add('disabled');
|
|
1699
|
+
|
|
1700
|
+
// Disable Copy, Pages, and Reload links
|
|
1701
|
+
['saveLink', 'pagesLink', 'resetLink'].forEach(function(id) {
|
|
1702
|
+
var el = document.getElementById(id);
|
|
1703
|
+
el.style.opacity = '0.4';
|
|
1704
|
+
el.style.pointerEvents = 'none';
|
|
1705
|
+
});
|
|
1706
|
+
|
|
1707
|
+
// Show firstRun greeting, hide default
|
|
1708
|
+
document.getElementById('defaultGreeting').style.display = 'none';
|
|
1709
|
+
document.getElementById('firstRunGreeting').style.display = '';
|
|
1710
|
+
|
|
1711
|
+
// Show banner and lock to models tab
|
|
1712
|
+
document.getElementById('configBanner').style.display = 'block';
|
|
1713
|
+
openSection('models');
|
|
1714
|
+
|
|
1715
|
+
// Disable other accordion tabs
|
|
1716
|
+
document.querySelector('.accordion-section[data-section="appearance"]').classList.add('disabled');
|
|
1717
|
+
document.querySelector('.accordion-section[data-section="connectors"]').classList.add('disabled');
|
|
1718
|
+
document.querySelector('.accordion-section[data-section="agents"]').classList.add('disabled');
|
|
1719
|
+
|
|
1720
|
+
// Clear any existing values so wizard starts fresh
|
|
1721
|
+
document.getElementById('provider-builder').value = '';
|
|
1722
|
+
document.getElementById('serviceApiKey-builder').value = '';
|
|
1723
|
+
document.getElementById('model-builder').innerHTML = '<option value="">Select a model</option>';
|
|
1724
|
+
document.getElementById('maxTokens-builder').value = '';
|
|
1725
|
+
document.getElementById('instructions-builder').value = '';
|
|
1726
|
+
document.getElementById('provider-chat').value = '';
|
|
1727
|
+
document.getElementById('serviceApiKey-chat').value = '';
|
|
1728
|
+
document.getElementById('model-chat').innerHTML = '<option value="">Select a model</option>';
|
|
1729
|
+
document.getElementById('maxTokens-chat').value = '';
|
|
1730
|
+
document.getElementById('instructions-chat').value = '';
|
|
1731
|
+
|
|
1732
|
+
// Add placeholder option to builder provider dropdown
|
|
1733
|
+
var builderProviderSelect = document.getElementById('provider-builder');
|
|
1734
|
+
var placeholder = document.createElement('option');
|
|
1735
|
+
placeholder.value = '';
|
|
1736
|
+
placeholder.textContent = 'Select a provider';
|
|
1737
|
+
placeholder.disabled = true;
|
|
1738
|
+
placeholder.selected = true;
|
|
1739
|
+
builderProviderSelect.insertBefore(placeholder, builderProviderSelect.firstChild);
|
|
1740
|
+
builderProviderSelect.value = '';
|
|
1741
|
+
|
|
1742
|
+
// Disable Apply button until configured
|
|
1743
|
+
document.getElementById('applyBtn').disabled = true;
|
|
1744
|
+
|
|
1745
|
+
updateWizardVisibility();
|
|
1746
|
+
}
|
|
1747
|
+
}).catch(function(error) {
|
|
1748
|
+
console.error('Error fetching settings:', error);
|
|
1749
|
+
});
|
|
1750
|
+
</script>
|
|
1751
|
+
<script id="page-helpers" src="/api/page-helpers.js?v=3" data-locked="true"></script>
|
|
1752
|
+
<script id="page-script" src="/api/page-script.js?v=3" data-locked="true"></script>
|
|
1753
|
+
</body></html>
|