synthos 0.6.0 → 0.7.1

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.
Files changed (153) hide show
  1. package/README.md +33 -1
  2. package/default-pages/app_builder.html +40 -0
  3. package/default-pages/app_builder.json +1 -0
  4. package/default-pages/json_tools.html +89 -159
  5. package/default-pages/json_tools.json +1 -0
  6. package/default-pages/my_notes.html +33 -0
  7. package/default-pages/my_notes.json +12 -0
  8. package/default-pages/neon_asteroids.html +77 -0
  9. package/default-pages/neon_asteroids.json +12 -0
  10. package/default-pages/sidebar_builder.html +49 -0
  11. package/default-pages/sidebar_builder.json +1 -0
  12. package/default-pages/solar_explorer.html +1956 -0
  13. package/default-pages/solar_explorer.json +12 -0
  14. package/default-pages/solar_tutorial.html +476 -0
  15. package/default-pages/solar_tutorial.json +1 -0
  16. package/default-pages/two-panel_builder.html +66 -0
  17. package/default-pages/two-panel_builder.json +1 -0
  18. package/default-themes/nebula-dawn.css +682 -0
  19. package/default-themes/nebula-dawn.json +19 -0
  20. package/default-themes/nebula-dusk.css +674 -0
  21. package/default-themes/nebula-dusk.json +19 -0
  22. package/dist/connectors/index.d.ts +3 -0
  23. package/dist/connectors/index.d.ts.map +1 -0
  24. package/dist/connectors/index.js +6 -0
  25. package/dist/connectors/index.js.map +1 -0
  26. package/dist/connectors/registry.d.ts +3 -0
  27. package/dist/connectors/registry.d.ts.map +1 -0
  28. package/dist/connectors/registry.js +100 -0
  29. package/dist/connectors/registry.js.map +1 -0
  30. package/dist/connectors/types.d.ts +61 -0
  31. package/dist/connectors/types.d.ts.map +1 -0
  32. package/dist/connectors/types.js +3 -0
  33. package/dist/connectors/types.js.map +1 -0
  34. package/dist/files.d.ts +2 -0
  35. package/dist/files.d.ts.map +1 -1
  36. package/dist/files.js +12 -1
  37. package/dist/files.js.map +1 -1
  38. package/dist/init.d.ts +8 -1
  39. package/dist/init.d.ts.map +1 -1
  40. package/dist/init.js +155 -3
  41. package/dist/init.js.map +1 -1
  42. package/dist/migrations.d.ts +11 -0
  43. package/dist/migrations.d.ts.map +1 -0
  44. package/dist/migrations.js +281 -0
  45. package/dist/migrations.js.map +1 -0
  46. package/dist/models/index.d.ts +3 -0
  47. package/dist/models/index.d.ts.map +1 -0
  48. package/dist/models/index.js +10 -0
  49. package/dist/models/index.js.map +1 -0
  50. package/dist/models/providers.d.ts +7 -0
  51. package/dist/models/providers.d.ts.map +1 -0
  52. package/dist/models/providers.js +33 -0
  53. package/dist/models/providers.js.map +1 -0
  54. package/dist/models/types.d.ts +21 -0
  55. package/dist/models/types.d.ts.map +1 -0
  56. package/dist/models/types.js +3 -0
  57. package/dist/models/types.js.map +1 -0
  58. package/dist/pages.d.ts +21 -2
  59. package/dist/pages.d.ts.map +1 -1
  60. package/dist/pages.js +202 -23
  61. package/dist/pages.js.map +1 -1
  62. package/dist/scripts.js +2 -2
  63. package/dist/scripts.js.map +1 -1
  64. package/dist/service/createCompletePrompt.d.ts +3 -2
  65. package/dist/service/createCompletePrompt.d.ts.map +1 -1
  66. package/dist/service/createCompletePrompt.js +11 -16
  67. package/dist/service/createCompletePrompt.js.map +1 -1
  68. package/dist/service/debugLog.d.ts +11 -0
  69. package/dist/service/debugLog.d.ts.map +1 -0
  70. package/dist/service/debugLog.js +26 -0
  71. package/dist/service/debugLog.js.map +1 -0
  72. package/dist/service/modelInstructions.d.ts +7 -0
  73. package/dist/service/modelInstructions.d.ts.map +1 -0
  74. package/dist/service/modelInstructions.js +16 -0
  75. package/dist/service/modelInstructions.js.map +1 -0
  76. package/dist/service/requiresSettings.d.ts +2 -2
  77. package/dist/service/requiresSettings.d.ts.map +1 -1
  78. package/dist/service/requiresSettings.js.map +1 -1
  79. package/dist/service/server.d.ts.map +1 -1
  80. package/dist/service/server.js +15 -0
  81. package/dist/service/server.js.map +1 -1
  82. package/dist/service/transformPage.d.ts +81 -2
  83. package/dist/service/transformPage.d.ts.map +1 -1
  84. package/dist/service/transformPage.js +672 -82
  85. package/dist/service/transformPage.js.map +1 -1
  86. package/dist/service/useApiRoutes.d.ts.map +1 -1
  87. package/dist/service/useApiRoutes.js +579 -13
  88. package/dist/service/useApiRoutes.js.map +1 -1
  89. package/dist/service/useConnectorRoutes.d.ts +4 -0
  90. package/dist/service/useConnectorRoutes.d.ts.map +1 -0
  91. package/dist/service/useConnectorRoutes.js +389 -0
  92. package/dist/service/useConnectorRoutes.js.map +1 -0
  93. package/dist/service/useDataRoutes.d.ts.map +1 -1
  94. package/dist/service/useDataRoutes.js +83 -70
  95. package/dist/service/useDataRoutes.js.map +1 -1
  96. package/dist/service/usePageRoutes.d.ts.map +1 -1
  97. package/dist/service/usePageRoutes.js +243 -38
  98. package/dist/service/usePageRoutes.js.map +1 -1
  99. package/dist/settings.d.ts +33 -4
  100. package/dist/settings.d.ts.map +1 -1
  101. package/dist/settings.js +108 -15
  102. package/dist/settings.js.map +1 -1
  103. package/dist/synthos-cli.d.ts.map +1 -1
  104. package/dist/synthos-cli.js +11 -1
  105. package/dist/synthos-cli.js.map +1 -1
  106. package/dist/themes.d.ts +9 -0
  107. package/dist/themes.d.ts.map +1 -0
  108. package/dist/themes.js +64 -0
  109. package/dist/themes.js.map +1 -0
  110. package/package.json +6 -3
  111. package/required-pages/builder.html +74 -0
  112. package/required-pages/builder.json +1 -0
  113. package/required-pages/pages.html +169 -126
  114. package/required-pages/pages.json +1 -0
  115. package/required-pages/settings.html +812 -156
  116. package/required-pages/settings.json +1 -0
  117. package/required-pages/synthos_apis.html +272 -0
  118. package/required-pages/synthos_apis.json +1 -0
  119. package/required-pages/synthos_scripts.html +87 -0
  120. package/required-pages/synthos_scripts.json +1 -0
  121. package/src/connectors/index.ts +12 -0
  122. package/src/connectors/registry.ts +98 -0
  123. package/src/connectors/types.ts +68 -0
  124. package/src/files.ts +11 -0
  125. package/src/init.ts +151 -5
  126. package/src/migrations.ts +266 -0
  127. package/src/models/index.ts +2 -0
  128. package/src/models/providers.ts +33 -0
  129. package/src/models/types.ts +23 -0
  130. package/src/pages.ts +234 -26
  131. package/src/scripts.ts +2 -2
  132. package/src/service/createCompletePrompt.ts +14 -18
  133. package/src/service/debugLog.ts +17 -0
  134. package/src/service/modelInstructions.ts +14 -0
  135. package/src/service/requiresSettings.ts +3 -3
  136. package/src/service/server.ts +19 -2
  137. package/src/service/transformPage.ts +709 -88
  138. package/src/service/useApiRoutes.ts +632 -16
  139. package/src/service/useConnectorRoutes.ts +427 -0
  140. package/src/service/useDataRoutes.ts +87 -71
  141. package/src/service/usePageRoutes.ts +237 -44
  142. package/src/settings.ts +143 -20
  143. package/src/synthos-cli.ts +11 -1
  144. package/src/themes.ts +71 -0
  145. package/default-pages/[application].html +0 -95
  146. package/default-pages/[markdown].html +0 -271
  147. package/default-pages/[sidebar].html +0 -114
  148. package/default-pages/[split-application].html +0 -118
  149. package/default-pages/solar_system.html +0 -432
  150. package/default-pages/space_invaders.html +0 -617
  151. package/required-pages/apis.html +0 -362
  152. package/required-pages/home.html +0 -126
  153. package/required-pages/scripts.html +0 -350
@@ -1,185 +1,841 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
1
+ <!DOCTYPE html><html lang="en"><head>
4
2
  <meta charset="UTF-8">
5
3
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
4
  <title>SynthOS - Settings</title>
7
- <style>
8
- /* Nebula Dusk Theme */
9
- :root { --bg-primary:#1a1a2e; --bg-secondary:#16213e; --bg-tertiary:#0f0f23; --accent-primary:#667eea; --accent-secondary:#764ba2; --accent-tertiary:#f093fb; --accent-glow:rgba(138,43,226,0.3); --text-primary:#e0e0e0; --text-secondary:#b794f6; --border-color:rgba(138,43,226,0.3); }
10
- * { margin:0; padding:0; box-sizing:border-box; }
11
- body { font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif; background:linear-gradient(135deg,var(--bg-primary) 0%,var(--bg-secondary) 50%,var(--bg-tertiary) 100%); color:var(--text-primary); height:100vh; display:flex; }
12
-
13
- .chat-panel { width:30%; background:linear-gradient(180deg,rgba(26,26,46,0.95) 0%,rgba(22,33,62,0.95) 100%); box-shadow:0 0 30px var(--accent-glow),inset 0 0 60px rgba(75,0,130,0.1); padding:20px; display:flex; flex-direction:column; border-right:1px solid var(--border-color); }
14
- .chat-header { font-size:24px; padding:15px; background:linear-gradient(135deg,var(--accent-primary) 0%,var(--accent-secondary) 50%,var(--accent-tertiary) 100%); color:white; text-align:center; border-radius:15px; box-shadow:0 4px 20px rgba(102,126,234,0.4); text-shadow:0 2px 10px rgba(0,0,0,0.3); letter-spacing:2px; }
15
- .chat-messages { flex-grow:1; overflow-y:auto; padding:15px; margin-top:15px; background:rgba(15,15,35,0.6); border-radius:15px; border:1px solid var(--border-color); box-shadow:inset 0 0 30px rgba(75,0,130,0.2); }
16
- .chat-message { margin-bottom:15px; padding:12px 15px; background:linear-gradient(135deg,rgba(102,126,234,0.15) 0%,rgba(118,75,162,0.15) 100%); border-radius:15px; box-shadow:0 2px 10px var(--accent-glow); border:1px solid rgba(138,43,226,0.1); backdrop-filter:blur(5px); }
17
- .chat-message p { margin-bottom:5px; line-height:1.5; }
18
- .chat-message p strong { font-weight:600; background:linear-gradient(90deg,var(--accent-primary),var(--accent-tertiary)); -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text; }
19
- .chat-message p code { background:rgba(138,43,226,0.3); padding:2px 6px; border-radius:5px; font-family:'Courier New',Courier,monospace; color:var(--accent-tertiary); border:1px solid rgba(240,147,251,0.3); }
20
-
21
- .link-group { display:flex; justify-content:space-between; margin:15px 0; padding:10px; background:rgba(15,15,35,0.4); border-radius:10px; border:1px solid var(--border-color); }
22
- .link-group a { font-size:14px; color:var(--text-secondary); text-decoration:none; padding:8px 15px; border-radius:8px; transition:all 0.3s ease; border:1px solid transparent; }
23
- .link-group a:hover { background:linear-gradient(135deg,rgba(102,126,234,0.3) 0%,rgba(118,75,162,0.3) 100%); border-color:rgba(183,148,246,0.5); box-shadow:0 0 15px var(--accent-glow); color:var(--accent-tertiary); }
24
-
25
- .chat-panel > form { display:flex; flex-direction:row; width:100%; gap:10px; align-items:center; }
26
- .chat-input { padding:14px 18px; border:1px solid var(--border-color); border-radius:25px; flex-grow:1; font-size:14px; background:rgba(15,15,35,0.8); color:var(--text-primary); box-shadow:inset 0 2px 10px rgba(0,0,0,0.3),0 0 20px rgba(138,43,226,0.1); transition:all 0.3s ease; }
27
- .chat-input:focus { outline:none; border-color:rgba(183,148,246,0.6); box-shadow:inset 0 2px 10px rgba(0,0,0,0.3),0 0 25px var(--accent-glow); }
28
- .chat-input::placeholder { color:rgba(183,148,246,0.5); }
29
-
30
- .chat-submit { padding:14px 20px; border:none; border-radius:25px; font-size:14px; background:linear-gradient(135deg,var(--accent-primary) 0%,var(--accent-secondary) 50%,var(--accent-tertiary) 100%); color:white; cursor:pointer; transition:all 0.3s ease; font-weight:600; letter-spacing:1px; box-shadow:0 4px 20px rgba(102,126,234,0.4); white-space:nowrap; }
31
- .chat-submit:hover { transform:translateY(-2px); box-shadow:0 6px 25px rgba(102,126,234,0.6); }
32
- .chat-submit:active { transform:translateY(0); }
33
-
34
- .viewer-panel { width:70%; padding:25px; background:linear-gradient(135deg,rgba(22,33,62,0.9) 0%,rgba(15,15,35,0.95) 100%); display:flex; flex-direction:column; justify-content:flex-start; align-items:center; box-shadow:inset 0 0 60px rgba(75,0,130,0.15); position:relative; overflow:hidden; }
35
- .viewer-panel::before { content:''; position:absolute; top:-50%; left:-50%; width:200%; height:200%; background:radial-gradient(ellipse at center,rgba(138,43,226,0.05) 0%,transparent 70%); animation:nebula-pulse 8s ease-in-out infinite; pointer-events:none; }
36
- @keyframes nebula-pulse { 0%,100%{opacity:0.5;transform:scale(1);} 50%{opacity:1;transform:scale(1.1);} }
37
-
38
- .loading-overlay { display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(15,15,35,0.9); justify-content:center; align-items:center; z-index:1000; }
39
- .spinner { width:50px; height:50px; border:4px solid var(--border-color); border-top-color:var(--accent-tertiary); border-radius:50%; animation:spin 1s linear infinite; }
40
- @keyframes spin { to{transform:rotate(360deg);} }
41
-
42
- /* Scrollbar Styles */
43
- ::-webkit-scrollbar { width:10px; height:10px; }
44
- ::-webkit-scrollbar-track { background:rgba(15,15,35,0.6); border-radius:10px; border:1px solid var(--border-color); }
45
- ::-webkit-scrollbar-thumb { background:linear-gradient(180deg,var(--accent-primary) 0%,var(--accent-secondary) 50%,var(--accent-tertiary) 100%); border-radius:10px; border:2px solid rgba(15,15,35,0.6); box-shadow:0 0 10px var(--accent-glow); }
46
- ::-webkit-scrollbar-thumb:hover { background:linear-gradient(180deg,var(--accent-tertiary) 0%,var(--accent-secondary) 50%,var(--accent-primary) 100%); box-shadow:0 0 15px rgba(240,147,251,0.5); }
47
- ::-webkit-scrollbar-corner { background:rgba(15,15,35,0.6); }
48
- * { scrollbar-width:thin; scrollbar-color:var(--accent-secondary) rgba(15,15,35,0.6); }
49
-
50
- /* Settings Form Styles - Nebula Theme */
51
- .settings-form { background:linear-gradient(135deg,rgba(26,26,46,0.95) 0%,rgba(22,33,62,0.95) 100%); border-radius:15px; padding:0; box-shadow:0 6px 30px var(--accent-glow),inset 0 0 40px rgba(75,0,130,0.1); border:1px solid var(--border-color); width:100%; max-width:500px; position:relative; z-index:1; overflow:hidden; }
52
- .dialog-title { font-size:22px; font-weight:700; padding:18px 30px; background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary)); color:white; text-align:center; box-shadow:0 4px 20px rgba(102,126,234,0.4); text-shadow:0 2px 10px rgba(0,0,0,0.3); letter-spacing:2px; }
53
- .dialog-content { padding:25px; background:rgba(15,15,35,0.5); }
54
- #settingsForm { display:flex; flex-direction:column; gap:15px; }
55
- .form-group { display:flex; flex-direction:column; width:100%; }
56
- .form-group label { display:block; margin-bottom:8px; color:var(--text-secondary); font-weight:600; font-size:14px; }
57
- .form-group input,.form-group select,.form-group textarea { width:100%; padding:14px 18px; border-radius:12px; border:1px solid var(--border-color); background:rgba(15,15,35,0.8); color:var(--text-primary); font-size:14px; transition:all 0.3s ease; box-shadow:inset 0 2px 10px rgba(0,0,0,0.3); }
58
- .form-group input:focus,.form-group select:focus,.form-group textarea:focus { outline:none; border-color:rgba(183,148,246,0.6); box-shadow:inset 0 2px 10px rgba(0,0,0,0.3),0 0 20px var(--accent-glow); }
59
- .form-group input::placeholder,.form-group textarea::placeholder { color:rgba(183,148,246,0.5); }
60
- .form-group textarea { resize:vertical; min-height:100px; }
61
- .form-group select { cursor:pointer; appearance:none; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23b794f6' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); background-repeat:no-repeat; background-position:right 15px center; }
62
- .form-group select option { background:var(--bg-tertiary); color:var(--text-primary); padding:10px; }
63
-
64
- .settings-form .update-btn { width:100%; padding:16px 20px; border:none; border-radius:12px; font-size:15px; background:linear-gradient(135deg,var(--accent-primary) 0%,var(--accent-secondary) 50%,var(--accent-tertiary) 100%); color:white; cursor:pointer; transition:all 0.3s ease; font-weight:600; letter-spacing:1px; box-shadow:0 4px 20px rgba(102,126,234,0.4); margin-top:5px; }
65
- .settings-form .update-btn:hover { transform:translateY(-2px); box-shadow:0 6px 25px rgba(102,126,234,0.6); }
66
- .settings-form .update-btn:active { transform:translateY(0); }
67
-
68
- .disabled { background:rgba(102,126,234,0.2)!important; cursor:not-allowed!important; opacity:0.6; }
69
- .info-text { color:rgba(183,148,246,0.7); margin-top:20px; font-size:13px; line-height:1.6; text-align:center; padding:15px; background:rgba(15,15,35,0.4); border-radius:10px; border:1px solid var(--border-color); }
70
- .info-text a { color:var(--accent-tertiary); text-decoration:none; transition:all 0.3s; font-weight:500; }
71
- .info-text a:hover { color:var(--text-secondary); text-shadow:0 0 10px var(--accent-glow); }
72
- </style>
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>.settings-title{font-size:22px;font-weight:700;min-height:var(--header-min-height);padding:var(--header-padding-vertical) var(--header-padding-horizontal);line-height:var(--header-line-height);display:flex;align-items:center;justify-content:center;box-sizing:border-box;background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:#fff;border-radius:12px 12px 0 0;width:100%;box-shadow:0 6px 25px var(--accent-glow);letter-spacing:2px;text-shadow:0 2px 10px rgba(0,0,0,.3)}.settings-container{display:flex;flex-direction:column;width:100%;flex-grow:1;background:rgba(15,15,35,.8);border-radius:0 0 12px 12px;border:1px solid rgba(138,43,226,.2);border-top:none;overflow:hidden}.accordion-section{display:flex;flex-direction:column;flex-shrink:0;overflow:hidden;border-bottom:1px solid var(--border-color)}.accordion-section:last-child{border-bottom:none}.accordion-section.active{flex-shrink:1;flex-grow:1;min-height:0}.accordion-header{display:flex;justify-content:space-between;align-items:center;width:100%;padding:16px 25px;background:none;border:none;color:var(--text-primary);font-size:15px;font-weight:600;cursor:pointer;transition:background .3s;letter-spacing:.5px;flex-shrink:0}.accordion-header:hover{background:rgba(138,43,226,.05)}.accordion-section.active .accordion-header{background:rgba(138,43,226,.08)}.accordion-chevron{font-size:11px;color:var(--text-secondary);transition:transform .3s}.accordion-section.active .accordion-chevron{transform:rotate(180deg)}.accordion-body{display:none;flex-direction:column;flex-grow:1;min-height:0;overflow:hidden}.accordion-section.active .accordion-body{display:flex}.accordion-content{display:flex;flex-direction:column;gap:15px;overflow-y:auto;padding:20px 25px;flex-grow:1}.button-row{display:flex;justify-content:flex-end;padding:15px 25px;border-top:1px solid var(--border-color);flex-shrink:0}.apply-btn{padding:12px 30px;border:none;border-radius:12px;font-size:15px;background:linear-gradient(135deg,var(--accent-primary) 0,var(--accent-secondary) 50%,var(--accent-tertiary) 100%);color:#fff;cursor:pointer;transition:.3s;font-weight:600;letter-spacing:1px;box-shadow:0 4px 20px rgba(102,126,234,.4)}.apply-btn:hover{transform:translateY(-2px);box-shadow:0 6px 25px rgba(102,126,234,.6)}.apply-btn:active{transform:translateY(0)}.form-group{display:flex;flex-direction:column;width:100%}.form-group label{display:block;margin-bottom:8px;color:var(--text-secondary);font-weight:600;font-size:14px}.form-group input,.form-group select,.form-group textarea{width:100%;padding:14px 18px;border-radius:12px;border:1px solid var(--border-color);background:rgba(15,15,35,.8);color:var(--text-primary);font-size:14px;transition:.3s;box-shadow:inset 0 2px 10px rgba(0,0,0,.3)}.form-group input:focus,.form-group select:focus,.form-group textarea:focus{outline:0;border-color:rgba(183,148,246,.6);box-shadow:inset 0 2px 10px rgba(0,0,0,.3),0 0 20px var(--accent-glow)}.form-group input::placeholder,.form-group textarea::placeholder{color:rgba(183,148,246,.5)}.form-group textarea{resize:vertical;min-height:100px}.form-group select{cursor:pointer;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23b794f6' d='M6 8L1 3h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 15px center}.form-group select option{background:var(--bg-tertiary);color:var(--text-primary);padding:10px}.info-text{color:rgba(183,148,246,.7);font-size:13px;line-height:1.6;text-align:center;padding:15px;background:rgba(15,15,35,.4);border-radius:10px;border:1px solid var(--border-color)}.info-text a{color:var(--accent-tertiary);text-decoration:none;transition:.3s;font-weight:500}.info-text a:hover{color:var(--text-secondary);text-shadow:0 0 10px var(--accent-glow)}.config-required-banner{padding:12px 20px;background:rgba(255,180,50,.12);border:1px solid rgba(255,180,50,.3);border-radius:10px;color:rgba(255,200,100,.9);font-size:13px;font-weight:500;text-align:center;line-height:1.5}.model-card{border:1px solid var(--border-color);border-radius:12px;padding:18px;display:flex;flex-direction:column;gap:12px;background:rgba(15,15,35,.3)}.model-card-title{font-size:14px;font-weight:700;color:var(--text-primary);letter-spacing:.5px;margin:0}.filter-bar{display:flex;align-items:center;gap:8px;flex-wrap:nowrap}.filter-btn{padding:6px 14px;border-radius:20px;border:1px solid var(--border-color);background:0 0;color:var(--text-secondary);font-size:13px;cursor:pointer;transition:.2s;white-space:nowrap;flex-shrink:0}.filter-btn:hover{border-color:var(--accent-primary);color:var(--accent-primary)}.filter-btn.active{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:#fff;border-color:transparent}.services-grid{display:grid;grid-template-columns:1fr;gap:12px}.service-card{border-radius:12px;border:1px solid var(--border-color);background:rgba(15,15,35,.5);padding:18px;transition:.3s}.service-card:hover{border-color:rgba(138,43,226,.3)}.service-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.service-card-name{font-size:15px;font-weight:600;color:var(--text-primary)}.service-card-category{font-size:11px;color:var(--text-secondary);background:rgba(138,43,226,.15);padding:2px 8px;border-radius:10px}.service-card-desc{font-size:13px;color:var(--text-secondary);line-height:1.5;margin-bottom:12px}.service-card-fields{display:flex;flex-direction:column;gap:8px}.service-card-fields input{width:100%;padding:10px 14px;border-radius:8px;border:1px solid var(--border-color);background:rgba(15,15,35,.8);color:var(--text-primary);font-size:13px;transition:.3s;box-shadow:inset 0 2px 8px rgba(0,0,0,.2)}.service-card-fields input:focus{outline:0;border-color:rgba(183,148,246,.6);box-shadow:inset 0 2px 8px rgba(0,0,0,.2),0 0 15px var(--accent-glow)}.service-card-fields input::placeholder{color:rgba(183,148,246,.5)}.toggle-switch{position:relative;width:44px;height:24px;flex-shrink:0}.toggle-switch input{opacity:0;width:0;height:0}.toggle-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:rgba(100,100,100,.4);border-radius:24px;transition:.3s}.toggle-slider:before{content:"";position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.3s}.toggle-switch input:checked+.toggle-slider{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary))}.toggle-switch input:checked+.toggle-slider:before{transform:translateX(20px)}.light-mode .settings-container{background:rgba(255,255,255,.8)}.light-mode .accordion-header:hover{background:rgba(118,75,162,.05)}.light-mode .accordion-section.active .accordion-header{background:rgba(118,75,162,.06)}.light-mode .form-group input,.light-mode .form-group select,.light-mode .form-group textarea{background:rgba(255,255,255,.8);box-shadow:inset 0 2px 10px rgba(118,75,162,.05)}.light-mode .form-group input:focus,.light-mode .form-group select:focus,.light-mode .form-group textarea:focus{box-shadow:inset 0 2px 10px rgba(118,75,162,.05),0 0 20px var(--accent-glow)}.light-mode .form-group input::placeholder,.light-mode .form-group textarea::placeholder{color:rgba(107,79,138,.5)}.light-mode .info-text{color:rgba(107,79,138,.7);background:rgba(255,255,255,.4)}.light-mode .config-required-banner{background:rgba(200,150,50,.1);border-color:rgba(200,150,50,.3);color:rgba(160,120,30,.9)}.light-mode .model-card{background:rgba(255,255,255,.3)}.light-mode .service-card{background:rgba(255,255,255,.5)}.light-mode .service-card-fields input{background:rgba(255,255,255,.8);box-shadow:inset 0 2px 8px rgba(118,75,162,.05)}.light-mode .service-card-fields input:focus{box-shadow:inset 0 2px 8px rgba(118,75,162,.05),0 0 15px var(--accent-glow)}.light-mode .service-card-fields input::placeholder{color:rgba(107,79,138,.5)}.model-card.disabled{opacity:0.35;pointer-events:none;user-select:none;transition:opacity .3s}.wizard-hidden{display:none !important}.more-settings-link{display:inline-block;color:var(--accent-tertiary);font-size:13px;cursor:pointer;text-decoration:none;padding:4px 0;transition:.3s;user-select:none}.more-settings-link:hover{color:var(--text-secondary);text-shadow:0 0 10px var(--accent-glow)}.light-mode .model-card.disabled{opacity:0.35}.accordion-section.disabled .accordion-header{opacity:0.35;pointer-events:none;cursor:default}.apply-btn:disabled{opacity:0.35;pointer-events:none;cursor:default}.connector-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}.connector-tile{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:20px 12px;border-radius:14px;border:1px solid var(--border-color);background:linear-gradient(160deg,rgba(30,30,60,.6),rgba(20,20,50,.8));cursor:pointer;transition:transform .2s,box-shadow .2s,border-color .2s;gap:8px}.connector-tile:hover{transform:translateY(-3px);box-shadow:0 6px 20px var(--accent-glow);border-color:var(--accent-primary)}.connector-tile.configured{background:linear-gradient(160deg,rgba(60,30,90,.6),rgba(40,20,70,.8));border-color:var(--accent-secondary);box-shadow:0 2px 12px var(--accent-glow)}.connector-tile-name{font-size:14px;font-weight:600;color:var(--text-primary)}.connector-category{font-size:11px;color:var(--text-secondary);background:rgba(138,43,226,.15);padding:2px 8px;border-radius:10px}.search-input{flex:1;min-width:100px;padding:6px 14px;border-radius:20px;border:1px solid var(--border-color);background:rgba(15,15,35,.8);color:var(--text-primary);font-size:13px;transition:.3s}.search-input:focus{outline:0;border-color:rgba(183,148,246,.6)}.search-input::placeholder{color:rgba(183,148,246,.5)}.filter-buttons-container{display:flex;gap:8px;flex-wrap:nowrap}.conn-modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;z-index:1000}.conn-modal-content{background:rgba(20,20,50,.95);border:1px solid var(--border-color);border-radius:16px;padding:28px;max-width:420px;width:90%;display:flex;flex-direction:column;gap:16px;box-shadow:0 8px 40px rgba(0,0,0,.5)}.conn-modal-title{font-size:18px;font-weight:700;color:var(--text-primary)}.conn-modal-desc{font-size:13px;color:var(--text-secondary);line-height:1.5}.conn-modal-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:8px}.conn-modal-btn{padding:12px 24px;border:none;border-radius:12px;font-size:14px;background:linear-gradient(135deg,var(--accent-primary) 0,var(--accent-secondary) 50%,var(--accent-tertiary) 100%);color:#fff;cursor:pointer;transition:.3s;font-weight:600;letter-spacing:.5px;box-shadow:0 4px 20px rgba(102,126,234,.4)}.conn-modal-btn:hover{transform:translateY(-2px);box-shadow:0 6px 25px rgba(102,126,234,.6)}.conn-modal-btn.cancel{background:rgba(100,100,100,.4);box-shadow:none}.conn-modal-btn.cancel:hover{background:rgba(100,100,100,.6);transform:translateY(-1px)}.conn-modal-btn.remove{background:rgba(180,50,50,.6);box-shadow:none}.conn-modal-btn.remove:hover{background:rgba(200,60,60,.8);transform:translateY(-1px)}.light-mode .connector-tile{background:linear-gradient(160deg,rgba(240,235,255,.8),rgba(250,248,255,.9))}.light-mode .connector-tile.configured{background:linear-gradient(160deg,rgba(220,200,255,.8),rgba(235,220,255,.9))}.light-mode .search-input{background:rgba(255,255,255,.8)}.light-mode .search-input::placeholder{color:rgba(107,79,138,.5)}.light-mode .conn-modal-content{background:rgba(250,248,255,.98)}</style>
73
8
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"></script>
74
9
  <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/14.1.1/marked.min.js"></script>
75
10
  <script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.1.0/mermaid.min.js"></script>
76
11
  </head>
77
12
  <body>
78
- <div class="chat-panel">
79
- <div class="chat-header">SynthOS</div>
80
- <div class="chat-messages" id="chatMessages">
81
- <div class="chat-message">
82
- <p><strong>SynthOS:</strong> Please fill in all required fields in the settings form and click 'Update'. Ensure your OpenAI or Anthropic API Key is valid and select the model you wish to use.</p>
83
- <p><code>Max Output Tokens</code> controls how large of a page that can be rendered and <code>Additional Instructions</code> can be used to provide further prompt instructions for how pages should be updated.</p>
13
+ <div class="chat-panel" data-locked="true">
14
+ <div class="chat-header" data-locked="true">SynthOS</div>
15
+ <div class="chat-messages" id="chatMessages" data-locked="true">
16
+ <div class="chat-message" id="defaultGreeting">
17
+ <p><strong>SynthOS:</strong> Configure your settings below. Use the accordion sections to navigate between General settings, Model configuration (Page Builder &amp; Chat Completion), and Additional Features.</p>
18
+ <p>The <strong>Page Builder Model</strong> is used when building pages via chat. The <strong>Chat Model</strong> is used by pages that call <code>synthos.generate.completion()</code>.</p>
19
+ </div>
20
+ <div class="chat-message" id="firstRunGreeting" style="display:none;">
21
+ <p><strong>SynthOS:</strong> Welcome to SynthOS! We're glad you're here.</p>
22
+ <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>
23
+ <p>You can always come back to this page later to change your theme or enable additional features.</p>
84
24
  </div>
85
25
  </div>
86
- <div class="link-group">
87
- <a href="#" id="saveLink">Save</a>
88
- <a href="/pages" id="pagesLink">Pages</a>
89
- <a href="#" id="resetLink">Reset</a>
26
+ <div class="link-group" data-locked="true">
27
+ <a href="#" id="saveLink" data-locked="true">Save</a>
28
+ <a href="/pages" id="pagesLink" data-locked="true">Pages</a>
29
+ <a href="#" id="resetLink" data-locked="true">Reset</a>
90
30
  </div>
91
- <form action="/" method="POST" id="chatForm">
92
- <input type="text" class="chat-input" id="chatInput" name="message" placeholder="Type a message...">
93
- <button type="submit" class="chat-submit">Send</button>
31
+ <form action="/" method="POST" id="chatForm" data-locked="true">
32
+ <input type="text" class="chat-input" id="chatInput" name="message" placeholder="Type a message..." data-locked="true">
33
+ <button type="submit" class="chat-submit" data-locked="true">Send</button>
94
34
  </form>
95
35
  </div>
96
- <div class="viewer-panel" id="viewerPanel">
97
- <div class="settings-form">
98
- <div class="dialog-title">Settings</div>
99
- <div class="dialog-content">
100
- <form action="/api/settings" method="POST" id="settingsForm">
101
- <div class="form-group">
102
- <label for="serviceApiKey">Service API Key</label>
103
- <input type="password" id="serviceApiKey" name="serviceApiKey" placeholder="Enter your API Key" required>
36
+ <div class="viewer-panel" id="viewerPanel" style="justify-content: flex-start; align-items: stretch;">
37
+ <div class="settings-title" style="max-width: none; border-radius: 12px 12px 0 0;">Settings</div>
38
+ <div class="settings-container" style="max-width: none; border-radius: 0; max-height: none; flex-grow: 1;">
39
+
40
+ <div class="accordion-section active" data-section="general">
41
+ <button class="accordion-header">
42
+ <span>General</span>
43
+ <span class="accordion-chevron">&#9662;</span>
44
+ </button>
45
+ <div class="accordion-body">
46
+ <div class="accordion-content">
47
+ <div class="form-group">
48
+ <label for="theme">Theme</label>
49
+ <select id="theme">
50
+ <option value="">Loading themes...</option>
51
+ </select>
52
+ </div>
104
53
  </div>
105
- <div class="form-group">
106
- <label for="model">Model</label>
107
- <select id="model" name="model" required>
108
- <option value="">Select a model</option>
109
- </select>
54
+ <div class="button-row">
55
+ <button class="apply-btn" data-apply="general">Apply</button>
110
56
  </div>
111
- <div class="form-group">
112
- <label for="maxTokens">Max Output Tokens</label>
113
- <input type="number" id="maxTokens" name="maxTokens" placeholder="Enter max token count" required>
57
+ </div>
58
+ </div>
59
+
60
+ <div class="accordion-section" data-section="models">
61
+ <button class="accordion-header">
62
+ <span>Page Building &amp; Chat</span>
63
+ <span class="accordion-chevron">&#9662;</span>
64
+ </button>
65
+ <div class="accordion-body">
66
+ <div class="accordion-content">
67
+ <div id="configBanner" class="config-required-banner" style="display:none;">
68
+ Model configuration is required before you can use SynthOS. Please fill in the Page Builder fields below and click Apply.
69
+ </div>
70
+
71
+ <div class="model-card" id="builderCard">
72
+ <div class="model-card-title">Page Builder Model</div>
73
+ <div class="form-group">
74
+ <label for="provider-builder">Provider</label>
75
+ <select id="provider-builder" required="">
76
+ <option value="">Select a provider</option>
77
+ </select>
78
+ </div>
79
+ <div class="form-group" id="fg-apikey-builder">
80
+ <label for="serviceApiKey-builder">API Key</label>
81
+ <input type="password" id="serviceApiKey-builder" placeholder="Enter your API Key" required="">
82
+ <div id="providerInstructions" class="info-text" style="display:none;margin-top:8px;"></div>
83
+ </div>
84
+ <div class="form-group" id="fg-model-builder">
85
+ <label for="model-builder">Model</label>
86
+ <select id="model-builder" required="">
87
+ <option value="">Select a model</option>
88
+ </select>
89
+ </div>
90
+ <a id="moreSettingsLink" class="more-settings-link">&#9662; More settings</a>
91
+ <div class="form-group" id="fg-maxTokens-builder">
92
+ <label for="maxTokens-builder">Max Output Tokens</label>
93
+ <input type="number" id="maxTokens-builder" placeholder="Enter max token count" required="">
94
+ </div>
95
+ <div class="form-group" id="fg-instructions-builder">
96
+ <label for="instructions-builder">Additional Instructions</label>
97
+ <textarea id="instructions-builder" placeholder="Enter any additional instructions"></textarea>
98
+ </div>
99
+ </div>
100
+
101
+ <div class="model-card" id="chatCard">
102
+ <div class="model-card-title">Chat Model</div>
103
+ <div class="form-group" id="fg-provider-chat">
104
+ <label for="provider-chat">Provider</label>
105
+ <select id="provider-chat" required="">
106
+ <option value="">Select a provider</option>
107
+ </select>
108
+ </div>
109
+ <div class="form-group" id="fg-apikey-chat">
110
+ <label for="serviceApiKey-chat">API Key</label>
111
+ <input type="password" id="serviceApiKey-chat" placeholder="Enter your API Key" required="">
112
+ </div>
113
+ <div class="form-group" id="fg-model-chat">
114
+ <label for="model-chat">Model</label>
115
+ <select id="model-chat" required="">
116
+ <option value="">Select a model</option>
117
+ </select>
118
+ </div>
119
+ <a id="moreSettingsLinkChat" class="more-settings-link">&#9662; More settings</a>
120
+ <div class="form-group" id="fg-maxTokens-chat">
121
+ <label for="maxTokens-chat">Max Output Tokens</label>
122
+ <input type="number" id="maxTokens-chat" placeholder="Enter max token count" required="">
123
+ </div>
124
+ <div class="form-group" id="fg-instructions-chat">
125
+ <label for="instructions-chat">Additional Instructions</label>
126
+ <textarea id="instructions-chat" placeholder="Enter any additional instructions"></textarea>
127
+ </div>
128
+ </div>
129
+
114
130
  </div>
115
- <div class="form-group">
116
- <label for="instructions">Additional Instructions</label>
117
- <textarea id="instructions" name="instructions" placeholder="Enter any additional instructions"></textarea>
131
+ <div class="button-row">
132
+ <button class="apply-btn" data-apply="models">Apply</button>
118
133
  </div>
119
- <button type="submit" class="chat-submit" action="/api/submit">Update</button>
120
- </form>
121
- <p class="info-text">An API Key is required which can be generated from either <a href="https://platform.openai.com/api-keys" target="_blank">OpenAI API Keys</a> or <a href="https://console.anthropic.com/settings/keys" target="_blank">Anthropic API Keys</a>.</p>
134
+ </div>
122
135
  </div>
136
+
137
+ <div class="accordion-section" data-section="connectors">
138
+ <button class="accordion-header">
139
+ <span>Connectors</span>
140
+ <span class="accordion-chevron">&#9662;</span>
141
+ </button>
142
+ <div class="accordion-body">
143
+ <div class="accordion-content">
144
+ <div class="filter-bar" id="connectorFilterBar">
145
+ <div class="filter-buttons-container" id="connectorFilterButtons"></div>
146
+ <input type="text" class="search-input" id="connectorSearch" placeholder="Search connectors...">
147
+ </div>
148
+ <div class="connector-grid" id="connectorGrid"></div>
149
+ </div>
150
+ </div>
151
+ </div>
152
+
123
153
  </div>
154
+ <div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div>
124
155
  </div>
125
- <div id="thoughts" style="display: none;">Ask the user to update their settings.</div>
126
- <div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div>
127
- <script>
128
- // Basic chat functionality
129
- document.getElementById("chatInput").focus();
130
- document.getElementById("chatForm").addEventListener('submit', (event) => {
131
- document.getElementById("loadingOverlay").style.display = 'flex';
132
- document.getElementById("chatForm").action = window.location.pathname;
133
- });
134
- document.getElementById("saveLink").addEventListener("click", function() {
135
- const pageName = prompt("Enter the name of the page to save as:");
136
- if (pageName) {
137
- window.location.href = `${window.location.pathname}/save?name=${encodeURIComponent(pageName)}`;
138
- }
156
+ <div class="conn-modal-overlay" id="connectorModal" style="display:none;">
157
+ <div class="conn-modal-content">
158
+ <div class="conn-modal-title" id="connectorModalTitle"></div>
159
+ <div class="conn-modal-desc" id="connectorModalDesc"></div>
160
+ <div id="connectorApiKeyGroup" class="form-group">
161
+ <label for="connectorApiKey">API Key</label>
162
+ <input type="password" id="connectorApiKey" placeholder="Enter your API key">
163
+ </div>
164
+ <div id="connectorOAuthGroup" style="display:none;">
165
+ <div id="connectorOAuthStatus" style="margin-bottom:12px;font-size:13px;line-height:1.5;"></div>
166
+ <div id="connectorOAuthModeToggle" style="display:flex;gap:8px;margin-bottom:12px;">
167
+ <button class="filter-btn active" id="oauthModeManual">Manual Token</button>
168
+ <button class="filter-btn" id="oauthModeApp">OAuth App</button>
169
+ </div>
170
+ <div id="connectorManualGroup">
171
+ <div class="form-group" style="margin-bottom:10px;">
172
+ <label for="oauth-field-accessToken">Access Token</label>
173
+ <input type="password" id="oauth-field-accessToken" placeholder="Paste token from Graph API Explorer">
174
+ </div>
175
+ <div class="form-group" style="margin-bottom:10px;">
176
+ <label for="oauth-field-userId">IG User ID <span style="color:var(--text-secondary);font-weight:400;">(optional)</span></label>
177
+ <input type="text" id="oauth-field-userId" placeholder="Instagram Business Account ID">
178
+ </div>
179
+ </div>
180
+ <div id="connectorAppGroup" style="display:none;">
181
+ <div class="form-group" id="connectorOAuthFields"></div>
182
+ <button class="conn-modal-btn" id="connectorConnectBtn" style="width:100%;margin-bottom:8px;">Connect with OAuth</button>
183
+ </div>
184
+ <button class="conn-modal-btn remove" id="connectorDisconnectBtn" style="width:100%;display:none;">Disconnect</button>
185
+ </div>
186
+ <div class="form-group" style="flex-direction:row;align-items:center;gap:12px;">
187
+ <label for="connectorEnabled" style="margin-bottom:0;">Enabled</label>
188
+ <label class="toggle-switch">
189
+ <input type="checkbox" id="connectorEnabled">
190
+ <span class="toggle-slider"></span>
191
+ </label>
192
+ </div>
193
+ <div class="conn-modal-actions">
194
+ <button class="conn-modal-btn" id="connectorSaveBtn">Save</button>
195
+ <button class="conn-modal-btn cancel" id="connectorCancelBtn">Cancel</button>
196
+ <button class="conn-modal-btn remove" id="connectorRemoveBtn">Remove</button>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ <div id="instructionsHidden" style="display: none;" data-locked="true"></div>
201
+ <div id="thoughts" style="display: none;" data-locked="true"></div>
202
+ <script id="settings-logic">
203
+ // --- Accordion logic ---
204
+ function openSection(sectionName) {
205
+ document.querySelectorAll('.accordion-section').forEach(function(s) {
206
+ s.classList.remove('active');
207
+ });
208
+ var target = document.querySelector('.accordion-section[data-section="' + sectionName + '"]');
209
+ if (target) target.classList.add('active');
210
+ }
211
+
212
+ document.querySelectorAll('.accordion-header').forEach(function(header) {
213
+ header.addEventListener('click', function() {
214
+ var section = header.closest('.accordion-section');
215
+ if (section.classList.contains('disabled')) return;
216
+ var sectionName = section.dataset.section;
217
+ var isActive = section.classList.contains('active');
218
+ document.querySelectorAll('.accordion-section').forEach(function(s) {
219
+ s.classList.remove('active');
139
220
  });
140
- document.getElementById("resetLink").addEventListener("click", function() {
141
- window.location.href = `${window.location.pathname}/reset`;
221
+ if (!isActive) {
222
+ section.classList.add('active');
223
+ // Update URL without reload
224
+ var url = new URL(window.location);
225
+ url.searchParams.set('tab', sectionName);
226
+ history.replaceState(null, '', url);
227
+ }
228
+ });
229
+ });
230
+
231
+ // --- URL param: open requested tab ---
232
+ var params = new URLSearchParams(window.location.search);
233
+ var tabParam = params.get('tab');
234
+ if (tabParam && ['general', 'models', 'connectors'].indexOf(tabParam) !== -1) {
235
+ openSection(tabParam);
236
+ }
237
+
238
+ // --- Connectors logic ---
239
+ var connectorList = [], activeConnectorCategory = 'All', currentConnectorId = null;
240
+
241
+ function loadConnectors() {
242
+ fetch('/api/connectors').then(function(r) { return r.json(); }).then(function(list) {
243
+ connectorList = list;
244
+ renderConnectorFilterBar();
245
+ renderConnectorGrid();
246
+ });
247
+ }
248
+
249
+ function renderConnectorFilterBar() {
250
+ var container = document.getElementById('connectorFilterButtons');
251
+ container.innerHTML = '';
252
+ var cats = new Set();
253
+ connectorList.forEach(function(c) { cats.add(c.category); });
254
+ var sorted = Array.from(cats).sort();
255
+ ['All'].concat(sorted).forEach(function(cat) {
256
+ var btn = document.createElement('button');
257
+ btn.className = 'filter-btn' + (activeConnectorCategory === cat ? ' active' : '');
258
+ btn.textContent = cat;
259
+ btn.addEventListener('click', function() {
260
+ activeConnectorCategory = cat;
261
+ renderConnectorFilterBar();
262
+ renderConnectorGrid();
142
263
  });
143
- window.onload = function() {
144
- const chatMessages = document.getElementById('chatMessages');
145
- chatMessages.scrollTop = chatMessages.scrollHeight;
264
+ container.appendChild(btn);
265
+ });
266
+ }
267
+
268
+ function renderConnectorGrid() {
269
+ var grid = document.getElementById('connectorGrid');
270
+ var searchTerm = (document.getElementById('connectorSearch').value || '').toLowerCase();
271
+ grid.innerHTML = '';
272
+ connectorList.filter(function(c) {
273
+ if (activeConnectorCategory !== 'All' && c.category !== activeConnectorCategory) return false;
274
+ if (searchTerm && c.name.toLowerCase().indexOf(searchTerm) === -1) return false;
275
+ return true;
276
+ }).forEach(function(c) {
277
+ var tile = document.createElement('div');
278
+ tile.className = 'connector-tile' + (c.configured ? ' configured' : '');
279
+ tile.addEventListener('click', function() { openConnectorModal(c.id); });
280
+
281
+ var name = document.createElement('div');
282
+ name.className = 'connector-tile-name';
283
+ name.textContent = c.name;
284
+
285
+ var cat = document.createElement('div');
286
+ cat.className = 'connector-category';
287
+ cat.textContent = c.category;
288
+
289
+ tile.appendChild(name);
290
+ tile.appendChild(cat);
291
+ grid.appendChild(tile);
292
+ });
293
+ }
294
+
295
+ var currentConnectorDetail = null;
296
+
297
+ function openConnectorModal(id) {
298
+ currentConnectorId = id;
299
+ fetch('/api/connectors/' + encodeURIComponent(id)).then(function(r) { return r.json(); }).then(function(detail) {
300
+ currentConnectorDetail = detail;
301
+ document.getElementById('connectorModalTitle').textContent = detail.name;
302
+ document.getElementById('connectorModalDesc').textContent = detail.description;
303
+ document.getElementById('connectorEnabled').checked = detail.enabled;
304
+ document.getElementById('connectorRemoveBtn').style.display = (detail.configured || detail.hasKey) ? '' : 'none';
305
+
306
+ var isOAuth = detail.authStrategy === 'oauth2';
307
+ document.getElementById('connectorApiKeyGroup').style.display = isOAuth ? 'none' : '';
308
+ document.getElementById('connectorOAuthGroup').style.display = isOAuth ? '' : 'none';
309
+
310
+ if (isOAuth) {
311
+ // Render OAuth App fields (App ID, App Secret)
312
+ var fieldsContainer = document.getElementById('connectorOAuthFields');
313
+ fieldsContainer.innerHTML = '';
314
+ (detail.fields || []).forEach(function(f) {
315
+ var wrap = document.createElement('div');
316
+ wrap.className = 'form-group';
317
+ wrap.style.marginBottom = '10px';
318
+ var lbl = document.createElement('label');
319
+ lbl.textContent = f.label;
320
+ lbl.setAttribute('for', 'oauth-field-' + f.name);
321
+ var inp = document.createElement('input');
322
+ inp.type = f.type;
323
+ inp.id = 'oauth-field-' + f.name;
324
+ inp.placeholder = detail.hasKey ? '(saved — leave blank to keep)' : 'Enter ' + f.label;
325
+ wrap.appendChild(lbl);
326
+ wrap.appendChild(inp);
327
+ fieldsContainer.appendChild(wrap);
328
+ });
329
+
330
+ // Reset manual token fields
331
+ document.getElementById('oauth-field-accessToken').value = '';
332
+ document.getElementById('oauth-field-accessToken').placeholder = detail.connected ? '(token saved — leave blank to keep)' : 'Paste token from Graph API Explorer';
333
+ document.getElementById('oauth-field-userId').value = '';
334
+ document.getElementById('oauth-field-userId').placeholder = detail.connected ? (detail.accountName || 'Instagram Business Account ID') : 'Instagram Business Account ID';
335
+
336
+ // Default to manual mode
337
+ setOAuthMode('manual');
338
+
339
+ // Show connected status
340
+ var statusEl = document.getElementById('connectorOAuthStatus');
341
+ var disconnectBtn = document.getElementById('connectorDisconnectBtn');
342
+ var modeToggle = document.getElementById('connectorOAuthModeToggle');
343
+ if (detail.connected) {
344
+ var displayName = detail.accountName || detail.name;
345
+ statusEl.innerHTML = '<span style="color:var(--accent-tertiary);">Connected as <strong>' + displayName + '</strong></span>';
346
+ statusEl.style.display = '';
347
+ disconnectBtn.style.display = '';
348
+ modeToggle.style.display = 'none';
349
+ } else {
350
+ statusEl.innerHTML = '';
351
+ statusEl.style.display = 'none';
352
+ disconnectBtn.style.display = 'none';
353
+ modeToggle.style.display = 'flex';
354
+ }
355
+ } else {
356
+ document.getElementById('connectorApiKey').value = '';
357
+ document.getElementById('connectorApiKey').placeholder = detail.hasKey ? '(key saved — leave blank to keep)' : 'Enter your API key';
358
+ }
359
+
360
+ document.getElementById('connectorModal').style.display = 'flex';
361
+ });
362
+ }
363
+
364
+ var currentOAuthMode = 'manual';
365
+
366
+ function setOAuthMode(mode) {
367
+ currentOAuthMode = mode;
368
+ document.getElementById('connectorManualGroup').style.display = mode === 'manual' ? '' : 'none';
369
+ document.getElementById('connectorAppGroup').style.display = mode === 'app' ? '' : 'none';
370
+ document.getElementById('oauthModeManual').className = 'filter-btn' + (mode === 'manual' ? ' active' : '');
371
+ document.getElementById('oauthModeApp').className = 'filter-btn' + (mode === 'app' ? ' active' : '');
372
+ }
373
+
374
+ document.getElementById('oauthModeManual').addEventListener('click', function() { setOAuthMode('manual'); });
375
+ document.getElementById('oauthModeApp').addEventListener('click', function() { setOAuthMode('app'); });
376
+
377
+ function closeConnectorModal() {
378
+ document.getElementById('connectorModal').style.display = 'none';
379
+ currentConnectorId = null;
380
+ }
381
+
382
+ document.getElementById('connectorCancelBtn').addEventListener('click', closeConnectorModal);
383
+
384
+ document.getElementById('connectorModal').addEventListener('click', function(e) {
385
+ if (e.target === this) closeConnectorModal();
386
+ });
387
+
388
+ document.getElementById('connectorSaveBtn').addEventListener('click', function() {
389
+ if (!currentConnectorId) return;
390
+ var isOAuth = currentConnectorDetail && currentConnectorDetail.authStrategy === 'oauth2';
391
+ var body;
392
+ if (isOAuth) {
393
+ body = { enabled: document.getElementById('connectorEnabled').checked };
394
+ if (currentOAuthMode === 'manual') {
395
+ // Manual token entry
396
+ var token = document.getElementById('oauth-field-accessToken').value;
397
+ if (token) body.accessToken = token;
398
+ var uid = document.getElementById('oauth-field-userId').value;
399
+ if (uid) body.userId = uid;
400
+ } else {
401
+ // OAuth app credentials
402
+ (currentConnectorDetail.fields || []).forEach(function(f) {
403
+ var el = document.getElementById('oauth-field-' + f.name);
404
+ if (el && el.value) body[f.name] = el.value;
405
+ });
406
+ }
407
+ } else {
408
+ body = {
409
+ apiKey: document.getElementById('connectorApiKey').value,
410
+ enabled: document.getElementById('connectorEnabled').checked
146
411
  };
412
+ }
413
+ fetch('/api/connectors/' + encodeURIComponent(currentConnectorId), {
414
+ method: 'POST',
415
+ headers: { 'Content-Type': 'application/json' },
416
+ body: JSON.stringify(body)
417
+ }).then(function(r) {
418
+ if (r.ok) { closeConnectorModal(); loadConnectors(); }
419
+ });
420
+ });
421
+
422
+ document.getElementById('connectorConnectBtn').addEventListener('click', function() {
423
+ if (!currentConnectorId || !currentConnectorDetail) return;
424
+ // Save credentials first, then redirect to authorize
425
+ var body = { enabled: true };
426
+ (currentConnectorDetail.fields || []).forEach(function(f) {
427
+ var el = document.getElementById('oauth-field-' + f.name);
428
+ if (el && el.value) body[f.name] = el.value;
429
+ });
430
+ fetch('/api/connectors/' + encodeURIComponent(currentConnectorId), {
431
+ method: 'POST',
432
+ headers: { 'Content-Type': 'application/json' },
433
+ body: JSON.stringify(body)
434
+ }).then(function(r) {
435
+ if (r.ok) {
436
+ window.location.href = '/api/connectors/' + encodeURIComponent(currentConnectorId) + '/authorize';
437
+ }
438
+ });
439
+ });
440
+
441
+ document.getElementById('connectorDisconnectBtn').addEventListener('click', function() {
442
+ if (!currentConnectorId) return;
443
+ fetch('/api/connectors/' + encodeURIComponent(currentConnectorId), { method: 'DELETE' }).then(function(r) {
444
+ if (r.ok) { closeConnectorModal(); loadConnectors(); }
445
+ });
446
+ });
447
+
448
+ document.getElementById('connectorRemoveBtn').addEventListener('click', function() {
449
+ if (!currentConnectorId) return;
450
+ fetch('/api/connectors/' + encodeURIComponent(currentConnectorId), { method: 'DELETE' }).then(function(r) {
451
+ if (r.ok) { closeConnectorModal(); loadConnectors(); }
452
+ });
453
+ });
454
+
455
+ document.getElementById('connectorSearch').addEventListener('input', renderConnectorGrid);
456
+
457
+ // Detect OAuth success/error from URL params
458
+ var connectedParam = params.get('connected');
459
+ var errorParam = params.get('error');
460
+ if (connectedParam) {
461
+ // Open the connectors tab and show success
462
+ openSection('connectors');
463
+ setTimeout(function() { openConnectorModal(connectedParam); }, 500);
464
+ // Clean URL
465
+ var cleanUrl = new URL(window.location);
466
+ cleanUrl.searchParams.delete('connected');
467
+ history.replaceState(null, '', cleanUrl);
468
+ }
469
+ if (errorParam) {
470
+ openSection('connectors');
471
+ alert('OAuth error: ' + errorParam);
472
+ var cleanUrl2 = new URL(window.location);
473
+ cleanUrl2.searchParams.delete('error');
474
+ history.replaceState(null, '', cleanUrl2);
475
+ }
476
+
477
+ // --- Save logic ---
478
+ function saveAllSettings() {
479
+ var builderApiKey = document.getElementById('serviceApiKey-builder').value;
480
+ var builderModel = document.getElementById('model-builder').value;
481
+ var builderMaxTokens = document.getElementById('maxTokens-builder').value;
482
+ var builderProvider = document.getElementById('provider-builder').value;
147
483
 
148
- // Form validation
149
- document.getElementById('settingsForm').addEventListener('submit', function(event) {
150
- const serviceApiKey = document.getElementById('serviceApiKey').value;
151
- const model = document.getElementById('model').value;
152
- const maxTokens = document.getElementById('maxTokens').value;
484
+ if (!builderProvider || !builderApiKey || !builderModel || !builderMaxTokens) {
485
+ alert('Please fill in all required Page Builder fields (Provider, API Key, Model, Max Output Tokens).');
486
+ openSection('models');
487
+ return;
488
+ }
153
489
 
154
- if (!serviceApiKey || !model || !maxTokens) {
155
- alert('Please fill in all required fields before submitting.');
156
- event.preventDefault();
490
+ var models = [
491
+ {
492
+ use: 'builder',
493
+ provider: builderProvider,
494
+ configuration: {
495
+ apiKey: builderApiKey,
496
+ model: builderModel,
497
+ maxTokens: builderMaxTokens
498
+ },
499
+ imageQuality: 'standard',
500
+ instructions: document.getElementById('instructions-builder').value
501
+ },
502
+ {
503
+ use: 'chat',
504
+ provider: document.getElementById('provider-chat').value,
505
+ configuration: {
506
+ apiKey: document.getElementById('serviceApiKey-chat').value,
507
+ model: document.getElementById('model-chat').value,
508
+ maxTokens: document.getElementById('maxTokens-chat').value
509
+ },
510
+ imageQuality: 'standard',
511
+ instructions: document.getElementById('instructions-chat').value
512
+ }
513
+ ];
514
+
515
+ var body = {
516
+ version: 2,
517
+ theme: document.getElementById('theme').value,
518
+ models: models,
519
+ features: []
520
+ };
521
+
522
+ fetch('/api/settings', {
523
+ method: 'POST',
524
+ headers: { 'Content-Type': 'application/json' },
525
+ body: JSON.stringify(body)
526
+ }).then(function(response) {
527
+ if (response.ok || response.redirected) {
528
+ if (wizardActive) {
529
+ window.location.href = '/builder?firstRun=true';
530
+ } else {
531
+ window.location.reload();
157
532
  }
533
+ } else {
534
+ alert('Failed to save settings. Please try again.');
535
+ }
536
+ }).catch(function(err) {
537
+ console.error('Error saving settings:', err);
538
+ alert('Failed to save settings. Please try again.');
539
+ });
540
+ }
541
+
542
+ document.querySelectorAll('.apply-btn').forEach(function(btn) {
543
+ btn.addEventListener('click', saveAllSettings);
544
+ });
545
+
546
+ // --- Provider helpers ---
547
+ var providersData = [];
548
+
549
+ function populateProviderDropdowns() {
550
+ var providerOptions = providersData.map(function(p) {
551
+ return '<option value="' + p.name + '">' + p.name + '</option>';
552
+ }).join('');
553
+ document.getElementById('provider-builder').innerHTML = providerOptions;
554
+ document.getElementById('provider-chat').innerHTML = providerOptions;
555
+ }
556
+
557
+ function populateModelDropdown(use, providerName) {
558
+ var provider = providersData.find(function(p) { return p.name === providerName; });
559
+ if (!provider) return;
560
+ var models = use === 'builder' ? provider.builderModels : provider.chatModels;
561
+ var options = models.map(function(m) {
562
+ return '<option value="' + m + '">' + m + '</option>';
563
+ }).join('');
564
+ document.getElementById('model-' + use).innerHTML = options;
565
+ }
566
+
567
+ document.getElementById('provider-builder').addEventListener('change', function() {
568
+ populateModelDropdown('builder', this.value);
569
+ });
570
+ document.getElementById('provider-chat').addEventListener('change', function() {
571
+ populateModelDropdown('chat', this.value);
572
+ });
573
+
574
+ // --- Provider signup instructions ---
575
+ var providerInstructions = {
576
+ '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>.',
577
+ '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>.'
578
+ };
579
+
580
+ // --- "More settings" toggle state (works for all users) ---
581
+ var builderAdvanced = false;
582
+ var chatAdvanced = false;
583
+
584
+ function applyAdvancedToggle() {
585
+ document.getElementById('fg-maxTokens-builder').classList.toggle('wizard-hidden', !builderAdvanced);
586
+ document.getElementById('fg-instructions-builder').classList.toggle('wizard-hidden', !builderAdvanced);
587
+ document.getElementById('fg-maxTokens-chat').classList.toggle('wizard-hidden', !chatAdvanced);
588
+ document.getElementById('fg-instructions-chat').classList.toggle('wizard-hidden', !chatAdvanced);
589
+ }
590
+
591
+ document.getElementById('moreSettingsLink').addEventListener('click', function(e) {
592
+ e.preventDefault();
593
+ builderAdvanced = !builderAdvanced;
594
+ this.innerHTML = builderAdvanced ? '&#9652; Less settings' : '&#9662; More settings';
595
+ applyAdvancedToggle();
596
+ });
597
+
598
+ document.getElementById('moreSettingsLinkChat').addEventListener('click', function(e) {
599
+ e.preventDefault();
600
+ chatAdvanced = !chatAdvanced;
601
+ this.innerHTML = chatAdvanced ? '&#9652; Less settings' : '&#9662; More settings';
602
+ applyAdvancedToggle();
603
+ });
604
+
605
+ // Hide advanced fields by default on load
606
+ applyAdvancedToggle();
607
+
608
+ // --- Wizard state ---
609
+ var wizardActive = false;
610
+ var builderStep = 0; // 0 = provider only, 1 = + API key, 2 = fully configured
611
+
612
+ function updateWizardVisibility() {
613
+ if (!wizardActive) return;
614
+ var fgApiKey = document.getElementById('fg-apikey-builder');
615
+ var fgModel = document.getElementById('fg-model-builder');
616
+ var moreLinkBuilder = document.getElementById('moreSettingsLink');
617
+ var chatCardEl = document.getElementById('chatCard');
618
+ var moreLinkChat = document.getElementById('moreSettingsLinkChat');
619
+
620
+ // Step 0: only provider visible
621
+ fgApiKey.classList.toggle('wizard-hidden', builderStep < 1);
622
+
623
+ // Model dropdown visible at step 2 (auto-selected)
624
+ fgModel.classList.toggle('wizard-hidden', builderStep < 2);
625
+
626
+ // Builder "More settings" hidden until step 2
627
+ moreLinkBuilder.style.display = builderStep >= 2 ? 'inline-block' : 'none';
628
+ if (builderStep < 2) {
629
+ document.getElementById('fg-maxTokens-builder').classList.add('wizard-hidden');
630
+ document.getElementById('fg-instructions-builder').classList.add('wizard-hidden');
631
+ } else {
632
+ // Let the toggle state drive visibility
633
+ applyAdvancedToggle();
634
+ }
635
+
636
+ // Chat card: before step 2, show only title grayed out
637
+ if (builderStep < 2) {
638
+ ['fg-provider-chat', 'fg-apikey-chat', 'fg-model-chat', 'fg-maxTokens-chat', 'fg-instructions-chat'].forEach(function(id) {
639
+ document.getElementById(id).classList.add('wizard-hidden');
640
+ });
641
+ moreLinkChat.style.display = 'none';
642
+ chatCardEl.classList.add('disabled');
643
+ } else {
644
+ // Step 2: show chat card populated — provider, key, model visible; advanced behind toggle
645
+ ['fg-provider-chat', 'fg-apikey-chat', 'fg-model-chat'].forEach(function(id) {
646
+ document.getElementById(id).classList.remove('wizard-hidden');
158
647
  });
648
+ moreLinkChat.style.display = 'inline-block';
649
+ applyAdvancedToggle();
650
+ chatCardEl.classList.remove('disabled');
651
+ }
652
+
653
+ updateApplyButton();
654
+ }
655
+
656
+ function updateApplyButton() {
657
+ var btns = document.querySelectorAll('.apply-btn');
658
+ if (!wizardActive) {
659
+ btns.forEach(function(b) { b.disabled = false; });
660
+ return;
661
+ }
662
+ var hasProvider = !!document.getElementById('provider-builder').value;
663
+ var hasKey = !!document.getElementById('serviceApiKey-builder').value;
664
+ var hasModel = !!document.getElementById('model-builder').value;
665
+ var hasTokens = !!document.getElementById('maxTokens-builder').value;
666
+ var ready = hasProvider && hasKey && hasModel && hasTokens;
667
+ btns.forEach(function(b) { b.disabled = !ready; });
668
+ }
669
+
670
+ function autoPopulateChatCard() {
671
+ var providerVal = document.getElementById('provider-builder').value;
672
+ var apiKeyVal = document.getElementById('serviceApiKey-builder').value;
673
+
674
+ document.getElementById('provider-chat').value = providerVal;
675
+ populateModelDropdown('chat', providerVal);
676
+
677
+ var chatModelSelect = document.getElementById('model-chat');
678
+ if (chatModelSelect.options.length > 0) {
679
+ chatModelSelect.selectedIndex = 0;
680
+ }
681
+
682
+ document.getElementById('serviceApiKey-chat').value = apiKeyVal;
683
+ document.getElementById('maxTokens-chat').value = '32000';
684
+ }
685
+
686
+ // --- Wizard event listeners ---
687
+ document.getElementById('provider-builder').addEventListener('change', function() {
688
+ if (!wizardActive) return;
689
+ var val = this.value;
690
+ if (!val) return;
691
+ if (builderStep < 1) builderStep = 1;
692
+ var instrEl = document.getElementById('providerInstructions');
693
+ if (providerInstructions[val]) {
694
+ instrEl.innerHTML = providerInstructions[val];
695
+ instrEl.style.display = 'block';
696
+ } else {
697
+ instrEl.style.display = 'none';
698
+ }
699
+ updateWizardVisibility();
700
+ });
701
+
702
+ document.getElementById('serviceApiKey-builder').addEventListener('input', function() {
703
+ if (!wizardActive) return;
704
+ if (this.value.length > 0 && builderStep < 2) {
705
+ builderStep = 2;
706
+ document.getElementById('providerInstructions').style.display = 'none';
707
+ document.getElementById('maxTokens-builder').value = '32000';
708
+ autoPopulateChatCard();
709
+ updateWizardVisibility();
710
+ }
711
+ updateApplyButton();
712
+ });
713
+
714
+ // --- Load data ---
715
+ var isConfigured = false;
716
+
717
+ Promise.all([
718
+ fetch('/api/settings').then(function(r) { return r.json(); }),
719
+ fetch('/api/themes').then(function(r) { return r.json(); })
720
+ ]).then(function(results) {
721
+ var data = results[0], themes = results[1];
722
+
723
+ // Store providers data
724
+ providersData = data.providers || [];
725
+
726
+ // Find builder and chat entries from models array
727
+ var models = data.models || [];
728
+ var builder = models.find(function(m) { return m.use === 'builder'; }) || {};
729
+ var chat = models.find(function(m) { return m.use === 'chat'; }) || {};
730
+
731
+ var builderConfig = builder.configuration || {};
732
+ var chatConfig = chat.configuration || {};
733
+ var builderApiKey = builderConfig.apiKey || '';
734
+ var builderModel = builderConfig.model || '';
735
+ var builderMaxTokens = builderConfig.maxTokens || '';
736
+ isConfigured = builderApiKey && builderModel && builderMaxTokens;
737
+
738
+ // Populate provider dropdowns
739
+ populateProviderDropdowns();
740
+
741
+ // Builder fields
742
+ document.getElementById('provider-builder').value = builder.provider || 'Anthropic';
743
+ populateModelDropdown('builder', builder.provider || 'Anthropic');
744
+ document.getElementById('serviceApiKey-builder').value = builderApiKey;
745
+ document.getElementById('model-builder').value = builderModel;
746
+ document.getElementById('maxTokens-builder').value = builderMaxTokens;
747
+ document.getElementById('instructions-builder').value = builder.instructions || '';
748
+
749
+ // Chat fields
750
+ document.getElementById('provider-chat').value = chat.provider || 'Anthropic';
751
+ populateModelDropdown('chat', chat.provider || 'Anthropic');
752
+ document.getElementById('serviceApiKey-chat').value = chatConfig.apiKey || '';
753
+ document.getElementById('model-chat').value = chatConfig.model || '';
754
+ document.getElementById('maxTokens-chat').value = chatConfig.maxTokens || '';
755
+ document.getElementById('instructions-chat').value = chat.instructions || '';
756
+
757
+ var currentTheme = data.theme || 'nebula-dusk';
758
+ document.getElementById('theme').innerHTML = themes.map(function(t) {
759
+ return '<option value="' + t + '">' + t + '</option>';
760
+ }).join('');
761
+ document.getElementById('theme').value = currentTheme;
762
+
763
+ loadConnectors();
764
+
765
+ var isFirstRun = params.get('firstRun') === '1';
766
+
767
+ if (!isConfigured) {
768
+ // Disable chat and navigation
769
+ document.getElementById('chatInput').disabled = true;
770
+ document.getElementById('chatInput').classList.add('disabled');
771
+ var pagesLink = document.getElementById('pagesLink');
772
+ pagesLink.style.opacity = '0.4';
773
+ pagesLink.style.pointerEvents = 'none';
774
+
775
+ // Show config required banner and open models section
776
+ document.getElementById('configBanner').style.display = 'block';
777
+ openSection('models');
778
+ }
779
+
780
+ if (isFirstRun) {
781
+ // Activate wizard — always treat as unconfigured when firstRun=1
782
+ wizardActive = true;
783
+ builderStep = 0;
784
+
785
+ // Disable chat and navigation (even if somehow configured)
786
+ document.getElementById('chatInput').disabled = true;
787
+ document.getElementById('chatInput').classList.add('disabled');
788
+
789
+ // Disable Copy, Pages, and Reload links
790
+ ['saveLink', 'pagesLink', 'resetLink'].forEach(function(id) {
791
+ var el = document.getElementById(id);
792
+ el.style.opacity = '0.4';
793
+ el.style.pointerEvents = 'none';
794
+ });
795
+
796
+ // Show firstRun greeting, hide default
797
+ document.getElementById('defaultGreeting').style.display = 'none';
798
+ document.getElementById('firstRunGreeting').style.display = '';
799
+
800
+ // Show banner and lock to models tab
801
+ document.getElementById('configBanner').style.display = 'block';
802
+ openSection('models');
803
+
804
+ // Disable other accordion tabs
805
+ document.querySelector('.accordion-section[data-section="general"]').classList.add('disabled');
806
+ document.querySelector('.accordion-section[data-section="connectors"]').classList.add('disabled');
807
+
808
+ // Clear any existing values so wizard starts fresh
809
+ document.getElementById('provider-builder').value = '';
810
+ document.getElementById('serviceApiKey-builder').value = '';
811
+ document.getElementById('model-builder').innerHTML = '<option value="">Select a model</option>';
812
+ document.getElementById('maxTokens-builder').value = '';
813
+ document.getElementById('instructions-builder').value = '';
814
+ document.getElementById('provider-chat').value = '';
815
+ document.getElementById('serviceApiKey-chat').value = '';
816
+ document.getElementById('model-chat').innerHTML = '<option value="">Select a model</option>';
817
+ document.getElementById('maxTokens-chat').value = '';
818
+ document.getElementById('instructions-chat').value = '';
819
+
820
+ // Add placeholder option to builder provider dropdown
821
+ var builderProviderSelect = document.getElementById('provider-builder');
822
+ var placeholder = document.createElement('option');
823
+ placeholder.value = '';
824
+ placeholder.textContent = 'Select a provider';
825
+ placeholder.disabled = true;
826
+ placeholder.selected = true;
827
+ builderProviderSelect.insertBefore(placeholder, builderProviderSelect.firstChild);
828
+ builderProviderSelect.value = '';
829
+
830
+ // Disable Apply buttons until configured
831
+ document.querySelectorAll('.apply-btn').forEach(function(b) { b.disabled = true; });
159
832
 
160
- // Fetch settings and populate form
161
- let isConfigured = false;
162
- fetch('/api/settings')
163
- .then(response => response.json())
164
- .then(data => {
165
- console.log('Settings:', data);
166
- const serviceApiKey = data.serviceApiKey || '';
167
- const model = data.model || '';
168
- const maxTokens = data.maxTokens || '';
169
- isConfigured = serviceApiKey && model && maxTokens;
170
- document.getElementById('serviceApiKey').value = serviceApiKey;
171
- document.getElementById('model').innerHTML = data.availableModels.map(model => `<option value="${model}">${model}</option>`).join('');
172
- document.getElementById('model').value = model;
173
- document.getElementById('maxTokens').value = maxTokens;
174
- document.getElementById('instructions').value = data.instructions || '';
175
-
176
- // Disable chat input if not configured
177
- if (!isConfigured) {
178
- document.getElementById('chatInput').disabled = true;
179
- document.getElementById('chatInput').classList.add('disabled');
180
- }
181
- })
182
- .catch(error => console.error('Error fetching settings:', error));
833
+ updateWizardVisibility();
834
+ }
835
+ }).catch(function(error) {
836
+ console.error('Error fetching settings:', error);
837
+ });
183
838
  </script>
184
- </body>
185
- </html>
839
+ <script id="page-helpers" src="/api/page-helpers.js?v=2" data-locked="true"></script>
840
+ <script id="page-script" src="/api/page-script.js?v=2" data-locked="true"></script>
841
+ </body></html>