@fraziym/axoniz 0.1.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.
Files changed (512) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +76 -0
  3. package/dist/agents/definition.d.ts +15 -0
  4. package/dist/agents/definition.d.ts.map +1 -0
  5. package/dist/agents/definition.js +41 -0
  6. package/dist/agents/definition.js.map +1 -0
  7. package/dist/agents/index.d.ts +12 -0
  8. package/dist/agents/index.d.ts.map +1 -0
  9. package/dist/agents/index.js +11 -0
  10. package/dist/agents/index.js.map +1 -0
  11. package/dist/agents/registry.d.ts +17 -0
  12. package/dist/agents/registry.d.ts.map +1 -0
  13. package/dist/agents/registry.js +23 -0
  14. package/dist/agents/registry.js.map +1 -0
  15. package/dist/agents/specialized.d.ts +31 -0
  16. package/dist/agents/specialized.d.ts.map +1 -0
  17. package/dist/agents/specialized.js +75 -0
  18. package/dist/agents/specialized.js.map +1 -0
  19. package/dist/awareness/index.d.ts +14 -0
  20. package/dist/awareness/index.d.ts.map +1 -0
  21. package/dist/awareness/index.js +22 -0
  22. package/dist/awareness/index.js.map +1 -0
  23. package/dist/awareness/service.d.ts +180 -0
  24. package/dist/awareness/service.d.ts.map +1 -0
  25. package/dist/awareness/service.js +789 -0
  26. package/dist/awareness/service.js.map +1 -0
  27. package/dist/cli/entry.d.ts +13 -0
  28. package/dist/cli/entry.d.ts.map +1 -0
  29. package/dist/cli/entry.js +35 -0
  30. package/dist/cli/entry.js.map +1 -0
  31. package/dist/comms/telegram.d.ts +41 -0
  32. package/dist/comms/telegram.d.ts.map +1 -0
  33. package/dist/comms/telegram.js +332 -0
  34. package/dist/comms/telegram.js.map +1 -0
  35. package/dist/core/agent.d.ts +252 -0
  36. package/dist/core/agent.d.ts.map +1 -0
  37. package/dist/core/agent.js +1399 -0
  38. package/dist/core/agent.js.map +1 -0
  39. package/dist/core/auth.d.ts +23 -0
  40. package/dist/core/auth.d.ts.map +1 -0
  41. package/dist/core/auth.js +113 -0
  42. package/dist/core/auth.js.map +1 -0
  43. package/dist/core/authority.d.ts +118 -0
  44. package/dist/core/authority.d.ts.map +1 -0
  45. package/dist/core/authority.js +402 -0
  46. package/dist/core/authority.js.map +1 -0
  47. package/dist/core/backend/async_queue.d.ts +17 -0
  48. package/dist/core/backend/async_queue.d.ts.map +1 -0
  49. package/dist/core/backend/async_queue.js +66 -0
  50. package/dist/core/backend/async_queue.js.map +1 -0
  51. package/dist/core/backend/index.d.ts +24 -0
  52. package/dist/core/backend/index.d.ts.map +1 -0
  53. package/dist/core/backend/index.js +128 -0
  54. package/dist/core/backend/index.js.map +1 -0
  55. package/dist/core/backend/llama_server.d.ts +39 -0
  56. package/dist/core/backend/llama_server.d.ts.map +1 -0
  57. package/dist/core/backend/llama_server.js +182 -0
  58. package/dist/core/backend/llama_server.js.map +1 -0
  59. package/dist/core/backend/llamacpp.d.ts +40 -0
  60. package/dist/core/backend/llamacpp.d.ts.map +1 -0
  61. package/dist/core/backend/llamacpp.js +200 -0
  62. package/dist/core/backend/llamacpp.js.map +1 -0
  63. package/dist/core/backend/lmstudio.d.ts +53 -0
  64. package/dist/core/backend/lmstudio.d.ts.map +1 -0
  65. package/dist/core/backend/lmstudio.js +274 -0
  66. package/dist/core/backend/lmstudio.js.map +1 -0
  67. package/dist/core/backend/ollama.d.ts +25 -0
  68. package/dist/core/backend/ollama.d.ts.map +1 -0
  69. package/dist/core/backend/ollama.js +86 -0
  70. package/dist/core/backend/ollama.js.map +1 -0
  71. package/dist/core/backend/openai.d.ts +27 -0
  72. package/dist/core/backend/openai.d.ts.map +1 -0
  73. package/dist/core/backend/openai.js +84 -0
  74. package/dist/core/backend/openai.js.map +1 -0
  75. package/dist/core/backend/types.d.ts +73 -0
  76. package/dist/core/backend/types.d.ts.map +1 -0
  77. package/dist/core/backend/types.js +137 -0
  78. package/dist/core/backend/types.js.map +1 -0
  79. package/dist/core/chat_store.d.ts +77 -0
  80. package/dist/core/chat_store.d.ts.map +1 -0
  81. package/dist/core/chat_store.js +332 -0
  82. package/dist/core/chat_store.js.map +1 -0
  83. package/dist/core/cli.d.ts +82 -0
  84. package/dist/core/cli.d.ts.map +1 -0
  85. package/dist/core/cli.js +602 -0
  86. package/dist/core/cli.js.map +1 -0
  87. package/dist/core/config.d.ts +127 -0
  88. package/dist/core/config.d.ts.map +1 -0
  89. package/dist/core/config.js +295 -0
  90. package/dist/core/config.js.map +1 -0
  91. package/dist/core/debug.d.ts +8 -0
  92. package/dist/core/debug.d.ts.map +1 -0
  93. package/dist/core/debug.js +8 -0
  94. package/dist/core/debug.js.map +1 -0
  95. package/dist/core/downloader.d.ts +31 -0
  96. package/dist/core/downloader.d.ts.map +1 -0
  97. package/dist/core/downloader.js +180 -0
  98. package/dist/core/downloader.js.map +1 -0
  99. package/dist/core/errors.d.ts +80 -0
  100. package/dist/core/errors.d.ts.map +1 -0
  101. package/dist/core/errors.js +134 -0
  102. package/dist/core/errors.js.map +1 -0
  103. package/dist/core/extras.d.ts +160 -0
  104. package/dist/core/extras.d.ts.map +1 -0
  105. package/dist/core/extras.js +553 -0
  106. package/dist/core/extras.js.map +1 -0
  107. package/dist/core/health.d.ts +27 -0
  108. package/dist/core/health.d.ts.map +1 -0
  109. package/dist/core/health.js +96 -0
  110. package/dist/core/health.js.map +1 -0
  111. package/dist/core/history.d.ts +50 -0
  112. package/dist/core/history.d.ts.map +1 -0
  113. package/dist/core/history.js +276 -0
  114. package/dist/core/history.js.map +1 -0
  115. package/dist/core/index.d.ts +6 -0
  116. package/dist/core/index.d.ts.map +1 -0
  117. package/dist/core/index.js +6 -0
  118. package/dist/core/index.js.map +1 -0
  119. package/dist/core/intelligence/ast_index.d.ts +255 -0
  120. package/dist/core/intelligence/ast_index.d.ts.map +1 -0
  121. package/dist/core/intelligence/ast_index.js +1369 -0
  122. package/dist/core/intelligence/ast_index.js.map +1 -0
  123. package/dist/core/intelligence/confidence.d.ts +113 -0
  124. package/dist/core/intelligence/confidence.d.ts.map +1 -0
  125. package/dist/core/intelligence/confidence.js +298 -0
  126. package/dist/core/intelligence/confidence.js.map +1 -0
  127. package/dist/core/intelligence/context_compressor.d.ts +80 -0
  128. package/dist/core/intelligence/context_compressor.d.ts.map +1 -0
  129. package/dist/core/intelligence/context_compressor.js +245 -0
  130. package/dist/core/intelligence/context_compressor.js.map +1 -0
  131. package/dist/core/intelligence/daemon.d.ts +247 -0
  132. package/dist/core/intelligence/daemon.d.ts.map +1 -0
  133. package/dist/core/intelligence/daemon.js +842 -0
  134. package/dist/core/intelligence/daemon.js.map +1 -0
  135. package/dist/core/intelligence/index.d.ts +60 -0
  136. package/dist/core/intelligence/index.d.ts.map +1 -0
  137. package/dist/core/intelligence/index.js +101 -0
  138. package/dist/core/intelligence/index.js.map +1 -0
  139. package/dist/core/intelligence/predictor.d.ts +246 -0
  140. package/dist/core/intelligence/predictor.d.ts.map +1 -0
  141. package/dist/core/intelligence/predictor.js +590 -0
  142. package/dist/core/intelligence/predictor.js.map +1 -0
  143. package/dist/core/intelligence/reflex.d.ts +84 -0
  144. package/dist/core/intelligence/reflex.d.ts.map +1 -0
  145. package/dist/core/intelligence/reflex.js +344 -0
  146. package/dist/core/intelligence/reflex.js.map +1 -0
  147. package/dist/core/intelligence/self_correction.d.ts +121 -0
  148. package/dist/core/intelligence/self_correction.d.ts.map +1 -0
  149. package/dist/core/intelligence/self_correction.js +327 -0
  150. package/dist/core/intelligence/self_correction.js.map +1 -0
  151. package/dist/core/intelligence/skill_distiller.d.ts +100 -0
  152. package/dist/core/intelligence/skill_distiller.d.ts.map +1 -0
  153. package/dist/core/intelligence/skill_distiller.js +246 -0
  154. package/dist/core/intelligence/skill_distiller.js.map +1 -0
  155. package/dist/core/intelligence/swarm.d.ts +330 -0
  156. package/dist/core/intelligence/swarm.d.ts.map +1 -0
  157. package/dist/core/intelligence/swarm.js +1064 -0
  158. package/dist/core/intelligence/swarm.js.map +1 -0
  159. package/dist/core/intelligence/trajectory.d.ts +107 -0
  160. package/dist/core/intelligence/trajectory.d.ts.map +1 -0
  161. package/dist/core/intelligence/trajectory.js +330 -0
  162. package/dist/core/intelligence/trajectory.js.map +1 -0
  163. package/dist/core/logger.d.ts +111 -0
  164. package/dist/core/logger.d.ts.map +1 -0
  165. package/dist/core/logger.js +640 -0
  166. package/dist/core/logger.js.map +1 -0
  167. package/dist/core/loop.d.ts +123 -0
  168. package/dist/core/loop.d.ts.map +1 -0
  169. package/dist/core/loop.js +423 -0
  170. package/dist/core/loop.js.map +1 -0
  171. package/dist/core/memory.d.ts +81 -0
  172. package/dist/core/memory.d.ts.map +1 -0
  173. package/dist/core/memory.js +328 -0
  174. package/dist/core/memory.js.map +1 -0
  175. package/dist/core/metrics.d.ts +29 -0
  176. package/dist/core/metrics.d.ts.map +1 -0
  177. package/dist/core/metrics.js +109 -0
  178. package/dist/core/metrics.js.map +1 -0
  179. package/dist/core/models.d.ts +51 -0
  180. package/dist/core/models.d.ts.map +1 -0
  181. package/dist/core/models.js +146 -0
  182. package/dist/core/models.js.map +1 -0
  183. package/dist/core/optimizer.d.ts +34 -0
  184. package/dist/core/optimizer.d.ts.map +1 -0
  185. package/dist/core/optimizer.js +131 -0
  186. package/dist/core/optimizer.js.map +1 -0
  187. package/dist/core/persona.d.ts +35 -0
  188. package/dist/core/persona.d.ts.map +1 -0
  189. package/dist/core/persona.js +126 -0
  190. package/dist/core/persona.js.map +1 -0
  191. package/dist/core/rate_limit.d.ts +23 -0
  192. package/dist/core/rate_limit.d.ts.map +1 -0
  193. package/dist/core/rate_limit.js +62 -0
  194. package/dist/core/rate_limit.js.map +1 -0
  195. package/dist/core/runner.d.ts +63 -0
  196. package/dist/core/runner.d.ts.map +1 -0
  197. package/dist/core/runner.js +671 -0
  198. package/dist/core/runner.js.map +1 -0
  199. package/dist/core/stream_parser.d.ts +43 -0
  200. package/dist/core/stream_parser.d.ts.map +1 -0
  201. package/dist/core/stream_parser.js +140 -0
  202. package/dist/core/stream_parser.js.map +1 -0
  203. package/dist/core/tool_schemas.d.ts +26 -0
  204. package/dist/core/tool_schemas.d.ts.map +1 -0
  205. package/dist/core/tool_schemas.js +124 -0
  206. package/dist/core/tool_schemas.js.map +1 -0
  207. package/dist/core/which.d.ts +2 -0
  208. package/dist/core/which.d.ts.map +1 -0
  209. package/dist/core/which.js +35 -0
  210. package/dist/core/which.js.map +1 -0
  211. package/dist/goals/index.d.ts +9 -0
  212. package/dist/goals/index.d.ts.map +1 -0
  213. package/dist/goals/index.js +17 -0
  214. package/dist/goals/index.js.map +1 -0
  215. package/dist/goals/service.d.ts +115 -0
  216. package/dist/goals/service.d.ts.map +1 -0
  217. package/dist/goals/service.js +415 -0
  218. package/dist/goals/service.js.map +1 -0
  219. package/dist/goals/types.d.ts +47 -0
  220. package/dist/goals/types.d.ts.map +1 -0
  221. package/dist/goals/types.js +66 -0
  222. package/dist/goals/types.js.map +1 -0
  223. package/dist/index.d.ts +22 -0
  224. package/dist/index.d.ts.map +1 -0
  225. package/dist/index.js +22 -0
  226. package/dist/index.js.map +1 -0
  227. package/dist/integrations/autonomous_startup.d.ts +51 -0
  228. package/dist/integrations/autonomous_startup.d.ts.map +1 -0
  229. package/dist/integrations/autonomous_startup.js +419 -0
  230. package/dist/integrations/autonomous_startup.js.map +1 -0
  231. package/dist/integrations/goose.d.ts +105 -0
  232. package/dist/integrations/goose.d.ts.map +1 -0
  233. package/dist/integrations/goose.js +408 -0
  234. package/dist/integrations/goose.js.map +1 -0
  235. package/dist/integrations/index.d.ts +21 -0
  236. package/dist/integrations/index.d.ts.map +1 -0
  237. package/dist/integrations/index.js +17 -0
  238. package/dist/integrations/index.js.map +1 -0
  239. package/dist/integrations/mcp.d.ts +73 -0
  240. package/dist/integrations/mcp.d.ts.map +1 -0
  241. package/dist/integrations/mcp.js +307 -0
  242. package/dist/integrations/mcp.js.map +1 -0
  243. package/dist/integrations/mempalace.d.ts +18 -0
  244. package/dist/integrations/mempalace.d.ts.map +1 -0
  245. package/dist/integrations/mempalace.js +25 -0
  246. package/dist/integrations/mempalace.js.map +1 -0
  247. package/dist/integrations/multi_agent.d.ts +82 -0
  248. package/dist/integrations/multi_agent.d.ts.map +1 -0
  249. package/dist/integrations/multi_agent.js +280 -0
  250. package/dist/integrations/multi_agent.js.map +1 -0
  251. package/dist/integrations/unified_memory.d.ts +415 -0
  252. package/dist/integrations/unified_memory.d.ts.map +1 -0
  253. package/dist/integrations/unified_memory.js +1489 -0
  254. package/dist/integrations/unified_memory.js.map +1 -0
  255. package/dist/roles/beru.yaml +48 -0
  256. package/dist/sidecar/client.d.ts +63 -0
  257. package/dist/sidecar/client.d.ts.map +1 -0
  258. package/dist/sidecar/client.js +222 -0
  259. package/dist/sidecar/client.js.map +1 -0
  260. package/dist/sidecar/index.d.ts +7 -0
  261. package/dist/sidecar/index.d.ts.map +1 -0
  262. package/dist/sidecar/index.js +6 -0
  263. package/dist/sidecar/index.js.map +1 -0
  264. package/dist/startup.d.ts +22 -0
  265. package/dist/startup.d.ts.map +1 -0
  266. package/dist/startup.js +194 -0
  267. package/dist/startup.js.map +1 -0
  268. package/dist/tools/_internal.d.ts +90 -0
  269. package/dist/tools/_internal.d.ts.map +1 -0
  270. package/dist/tools/_internal.js +375 -0
  271. package/dist/tools/_internal.js.map +1 -0
  272. package/dist/tools/_smoke.d.ts +2 -0
  273. package/dist/tools/_smoke.d.ts.map +1 -0
  274. package/dist/tools/_smoke.js +203 -0
  275. package/dist/tools/_smoke.js.map +1 -0
  276. package/dist/tools/axodex_tools.d.ts +51 -0
  277. package/dist/tools/axodex_tools.d.ts.map +1 -0
  278. package/dist/tools/axodex_tools.js +181 -0
  279. package/dist/tools/axodex_tools.js.map +1 -0
  280. package/dist/tools/code_tools.d.ts +33 -0
  281. package/dist/tools/code_tools.d.ts.map +1 -0
  282. package/dist/tools/code_tools.js +191 -0
  283. package/dist/tools/code_tools.js.map +1 -0
  284. package/dist/tools/computer_tools.d.ts +59 -0
  285. package/dist/tools/computer_tools.d.ts.map +1 -0
  286. package/dist/tools/computer_tools.js +348 -0
  287. package/dist/tools/computer_tools.js.map +1 -0
  288. package/dist/tools/file_tools.d.ts +51 -0
  289. package/dist/tools/file_tools.d.ts.map +1 -0
  290. package/dist/tools/file_tools.js +424 -0
  291. package/dist/tools/file_tools.js.map +1 -0
  292. package/dist/tools/index.d.ts +13 -0
  293. package/dist/tools/index.d.ts.map +1 -0
  294. package/dist/tools/index.js +13 -0
  295. package/dist/tools/index.js.map +1 -0
  296. package/dist/tools/shell_tools.d.ts +39 -0
  297. package/dist/tools/shell_tools.d.ts.map +1 -0
  298. package/dist/tools/shell_tools.js +163 -0
  299. package/dist/tools/shell_tools.js.map +1 -0
  300. package/dist/tools/web_tools.d.ts +31 -0
  301. package/dist/tools/web_tools.d.ts.map +1 -0
  302. package/dist/tools/web_tools.js +117 -0
  303. package/dist/tools/web_tools.js.map +1 -0
  304. package/dist/voice/index.d.ts +41 -0
  305. package/dist/voice/index.d.ts.map +1 -0
  306. package/dist/voice/index.js +58 -0
  307. package/dist/voice/index.js.map +1 -0
  308. package/dist/voice/light_daemon.d.ts +197 -0
  309. package/dist/voice/light_daemon.d.ts.map +1 -0
  310. package/dist/voice/light_daemon.js +746 -0
  311. package/dist/voice/light_daemon.js.map +1 -0
  312. package/dist/voice/setup.d.ts +63 -0
  313. package/dist/voice/setup.d.ts.map +1 -0
  314. package/dist/voice/setup.js +443 -0
  315. package/dist/voice/setup.js.map +1 -0
  316. package/dist/voice/stt.d.ts +148 -0
  317. package/dist/voice/stt.d.ts.map +1 -0
  318. package/dist/voice/stt.js +361 -0
  319. package/dist/voice/stt.js.map +1 -0
  320. package/dist/voice/tts.d.ts +168 -0
  321. package/dist/voice/tts.d.ts.map +1 -0
  322. package/dist/voice/tts.js +698 -0
  323. package/dist/voice/tts.js.map +1 -0
  324. package/dist/voice/voice_loop.d.ts +101 -0
  325. package/dist/voice/voice_loop.d.ts.map +1 -0
  326. package/dist/voice/voice_loop.js +463 -0
  327. package/dist/voice/voice_loop.js.map +1 -0
  328. package/dist/voice/wake_word.d.ts +153 -0
  329. package/dist/voice/wake_word.d.ts.map +1 -0
  330. package/dist/voice/wake_word.js +458 -0
  331. package/dist/voice/wake_word.js.map +1 -0
  332. package/dist/voice/wav.d.ts +99 -0
  333. package/dist/voice/wav.d.ts.map +1 -0
  334. package/dist/voice/wav.js +306 -0
  335. package/dist/voice/wav.js.map +1 -0
  336. package/dist/web/broker.d.ts +33 -0
  337. package/dist/web/broker.d.ts.map +1 -0
  338. package/dist/web/broker.js +58 -0
  339. package/dist/web/broker.js.map +1 -0
  340. package/dist/web/open_browser.d.ts +2 -0
  341. package/dist/web/open_browser.d.ts.map +1 -0
  342. package/dist/web/open_browser.js +42 -0
  343. package/dist/web/open_browser.js.map +1 -0
  344. package/dist/web/server.d.ts +54 -0
  345. package/dist/web/server.d.ts.map +1 -0
  346. package/dist/web/server.js +831 -0
  347. package/dist/web/server.js.map +1 -0
  348. package/dist/web/static/app.js +4121 -0
  349. package/dist/web/static/index.html +2281 -0
  350. package/dist/web/static/js/MODULE_SUMMARY.md +124 -0
  351. package/dist/web/static/js/a11y.js +165 -0
  352. package/dist/web/static/js/admin.js +2145 -0
  353. package/dist/web/static/js/assistant.js +475 -0
  354. package/dist/web/static/js/calendar/reminders.js +114 -0
  355. package/dist/web/static/js/calendar/utils.js +166 -0
  356. package/dist/web/static/js/calendar.js +3348 -0
  357. package/dist/web/static/js/censor.js +356 -0
  358. package/dist/web/static/js/chat.js +4546 -0
  359. package/dist/web/static/js/chatRenderer.js +2294 -0
  360. package/dist/web/static/js/chatStream.js +276 -0
  361. package/dist/web/static/js/codeRunner.js +398 -0
  362. package/dist/web/static/js/color/hex.js +14 -0
  363. package/dist/web/static/js/colorPicker.js +453 -0
  364. package/dist/web/static/js/compare/icons.js +77 -0
  365. package/dist/web/static/js/compare/index.js +1482 -0
  366. package/dist/web/static/js/compare/models.js +104 -0
  367. package/dist/web/static/js/compare/panes.js +817 -0
  368. package/dist/web/static/js/compare/probe.js +78 -0
  369. package/dist/web/static/js/compare/scoreboard.js +223 -0
  370. package/dist/web/static/js/compare/selector.js +1335 -0
  371. package/dist/web/static/js/compare/state.js +58 -0
  372. package/dist/web/static/js/compare/stream.js +695 -0
  373. package/dist/web/static/js/compare/vote.js +254 -0
  374. package/dist/web/static/js/cookbook-diagnosis.js +694 -0
  375. package/dist/web/static/js/cookbook-hwfit.js +1888 -0
  376. package/dist/web/static/js/cookbook.js +2093 -0
  377. package/dist/web/static/js/cookbookDownload.js +633 -0
  378. package/dist/web/static/js/cookbookProgressSignal.js +29 -0
  379. package/dist/web/static/js/cookbookRunning.js +3506 -0
  380. package/dist/web/static/js/cookbookServe.js +2128 -0
  381. package/dist/web/static/js/document.js +9731 -0
  382. package/dist/web/static/js/documentLibrary.js +3376 -0
  383. package/dist/web/static/js/dragSort.js +265 -0
  384. package/dist/web/static/js/editor/ai-inpaint.js +374 -0
  385. package/dist/web/static/js/editor/ai-models.js +273 -0
  386. package/dist/web/static/js/editor/ai-rembg.js +230 -0
  387. package/dist/web/static/js/editor/ai-tool-runner.js +147 -0
  388. package/dist/web/static/js/editor/ai-tools-misc.js +216 -0
  389. package/dist/web/static/js/editor/build/controls.js +366 -0
  390. package/dist/web/static/js/editor/build/popups.js +112 -0
  391. package/dist/web/static/js/editor/build/right-panel.js +200 -0
  392. package/dist/web/static/js/editor/build/toolbar.js +73 -0
  393. package/dist/web/static/js/editor/build/topbar.js +131 -0
  394. package/dist/web/static/js/editor/build/transform-popup.js +109 -0
  395. package/dist/web/static/js/editor/canvas-coords.js +21 -0
  396. package/dist/web/static/js/editor/canvas-events.js +197 -0
  397. package/dist/web/static/js/editor/canvas-transforms.js +132 -0
  398. package/dist/web/static/js/editor/checkerboard.js +24 -0
  399. package/dist/web/static/js/editor/clipboard-and-drop.js +133 -0
  400. package/dist/web/static/js/editor/composite-helpers.js +83 -0
  401. package/dist/web/static/js/editor/filters/blur.js +118 -0
  402. package/dist/web/static/js/editor/filters/edge-feather.js +70 -0
  403. package/dist/web/static/js/editor/fx/adj-popup.js +677 -0
  404. package/dist/web/static/js/editor/fx/filter-string.js +38 -0
  405. package/dist/web/static/js/editor/fx/histogram.js +67 -0
  406. package/dist/web/static/js/editor/fx/pixel-pass.js +249 -0
  407. package/dist/web/static/js/editor/harmonize-masks.js +116 -0
  408. package/dist/web/static/js/editor/history-panel.js +176 -0
  409. package/dist/web/static/js/editor/keyboard-shortcuts.js +271 -0
  410. package/dist/web/static/js/editor/layer-helpers.js +131 -0
  411. package/dist/web/static/js/editor/layer-panel.js +600 -0
  412. package/dist/web/static/js/editor/mask-utils.js +91 -0
  413. package/dist/web/static/js/editor/shortcuts-popover.js +149 -0
  414. package/dist/web/static/js/editor/slider-ux.js +191 -0
  415. package/dist/web/static/js/editor/snap.js +107 -0
  416. package/dist/web/static/js/editor/state.js +257 -0
  417. package/dist/web/static/js/editor/stroke-pipeline.js +162 -0
  418. package/dist/web/static/js/editor/stroke-tool-sliders.js +64 -0
  419. package/dist/web/static/js/editor/tools/clone.js +81 -0
  420. package/dist/web/static/js/editor/tools/crop.js +137 -0
  421. package/dist/web/static/js/editor/tools/flood-fill.js +72 -0
  422. package/dist/web/static/js/editor/tools/lasso-mask.js +171 -0
  423. package/dist/web/static/js/editor/tools/lasso.js +65 -0
  424. package/dist/web/static/js/editor/tools/move.js +79 -0
  425. package/dist/web/static/js/editor/tools/stroke.js +123 -0
  426. package/dist/web/static/js/editor/tools/transform-drag.js +174 -0
  427. package/dist/web/static/js/editor/tools/transform-handles.js +271 -0
  428. package/dist/web/static/js/editor/tools/transform-session.js +381 -0
  429. package/dist/web/static/js/editor/tools/wand.js +46 -0
  430. package/dist/web/static/js/editor/wire-import.js +148 -0
  431. package/dist/web/static/js/editor/wire-inpaint-controls.js +164 -0
  432. package/dist/web/static/js/editor/wire-merge-buttons.js +103 -0
  433. package/dist/web/static/js/editor/wire-selection-controls.js +170 -0
  434. package/dist/web/static/js/editor/wire-topbar-menus.js +174 -0
  435. package/dist/web/static/js/editor/wire-topbar-overflow.js +49 -0
  436. package/dist/web/static/js/editor/wire-topbar.js +188 -0
  437. package/dist/web/static/js/emailInbox.js +1245 -0
  438. package/dist/web/static/js/emailLibrary/replyRecipients.js +27 -0
  439. package/dist/web/static/js/emailLibrary/signatureFold.js +339 -0
  440. package/dist/web/static/js/emailLibrary/state.js +34 -0
  441. package/dist/web/static/js/emailLibrary/utils.js +202 -0
  442. package/dist/web/static/js/emailLibrary.js +5217 -0
  443. package/dist/web/static/js/emojiPicker.js +311 -0
  444. package/dist/web/static/js/escMenuStack.js +102 -0
  445. package/dist/web/static/js/fileHandler.js +295 -0
  446. package/dist/web/static/js/gallery.js +2835 -0
  447. package/dist/web/static/js/galleryEditor.js +3798 -0
  448. package/dist/web/static/js/group.js +955 -0
  449. package/dist/web/static/js/init.js +405 -0
  450. package/dist/web/static/js/keyboard-shortcuts.js +292 -0
  451. package/dist/web/static/js/langIcons.js +187 -0
  452. package/dist/web/static/js/markdown/tableRow.js +19 -0
  453. package/dist/web/static/js/markdown.js +806 -0
  454. package/dist/web/static/js/memory.js +1418 -0
  455. package/dist/web/static/js/modalManager.js +1552 -0
  456. package/dist/web/static/js/modalSnap.js +782 -0
  457. package/dist/web/static/js/model/matchKey.js +19 -0
  458. package/dist/web/static/js/modelPicker.js +695 -0
  459. package/dist/web/static/js/modelSort.js +33 -0
  460. package/dist/web/static/js/models.js +642 -0
  461. package/dist/web/static/js/notes.js +5054 -0
  462. package/dist/web/static/js/package.json +1 -0
  463. package/dist/web/static/js/platform.js +47 -0
  464. package/dist/web/static/js/presets.js +1110 -0
  465. package/dist/web/static/js/providers.js +93 -0
  466. package/dist/web/static/js/rag.js +177 -0
  467. package/dist/web/static/js/research/jobs.js +335 -0
  468. package/dist/web/static/js/research/panel.js +1233 -0
  469. package/dist/web/static/js/researchSynapse.js +225 -0
  470. package/dist/web/static/js/search-chat.js +201 -0
  471. package/dist/web/static/js/search.js +52 -0
  472. package/dist/web/static/js/section-management.js +260 -0
  473. package/dist/web/static/js/sessions.js +3108 -0
  474. package/dist/web/static/js/settings.js +4991 -0
  475. package/dist/web/static/js/sidebar-layout.js +573 -0
  476. package/dist/web/static/js/signature.js +504 -0
  477. package/dist/web/static/js/skills.js +1908 -0
  478. package/dist/web/static/js/slashAutocomplete.js +267 -0
  479. package/dist/web/static/js/slashCommands.js +6099 -0
  480. package/dist/web/static/js/spinner.js +406 -0
  481. package/dist/web/static/js/storage.js +123 -0
  482. package/dist/web/static/js/tasks.js +2699 -0
  483. package/dist/web/static/js/theme.js +2086 -0
  484. package/dist/web/static/js/tileManager.js +386 -0
  485. package/dist/web/static/js/tourAutoplay.js +133 -0
  486. package/dist/web/static/js/tourHints.js +179 -0
  487. package/dist/web/static/js/tts-ai.js +521 -0
  488. package/dist/web/static/js/ui.js +1288 -0
  489. package/dist/web/static/js/util/ordinal.js +13 -0
  490. package/dist/web/static/js/voiceRecorder.js +283 -0
  491. package/dist/web/static/js/windowDrag.js +326 -0
  492. package/dist/web/static/js/windowResize.js +233 -0
  493. package/dist/web/static/landing.html +824 -0
  494. package/dist/web/static/lib/docx.umd.min.js +20116 -0
  495. package/dist/web/static/lib/highlight.min.js +1213 -0
  496. package/dist/web/static/lib/html2pdf.bundle.min.js +3 -0
  497. package/dist/web/static/lib/mammoth.browser.min.js +21 -0
  498. package/dist/web/static/lib/qrcode.min.js +8 -0
  499. package/dist/web/static/lib/xlsx.full.min.js +24 -0
  500. package/dist/web/static/login.html +582 -0
  501. package/dist/web/static/manifest.json +15 -0
  502. package/dist/web/static/style.css +35733 -0
  503. package/dist/web/static/sw.js +144 -0
  504. package/dist/workflows/engine.d.ts +162 -0
  505. package/dist/workflows/engine.d.ts.map +1 -0
  506. package/dist/workflows/engine.js +625 -0
  507. package/dist/workflows/engine.js.map +1 -0
  508. package/dist/workflows/index.d.ts +8 -0
  509. package/dist/workflows/index.d.ts.map +1 -0
  510. package/dist/workflows/index.js +14 -0
  511. package/dist/workflows/index.js.map +1 -0
  512. package/package.json +112 -0
@@ -0,0 +1,1110 @@
1
+ // static/js/presets.js
2
+
3
+ /**
4
+ * Preset management
5
+ */
6
+
7
+ let API_BASE = '';
8
+ let selectedPreset = null;
9
+ let presets = {};
10
+
11
+ export function loadStoredArray(key) {
12
+ try {
13
+ const value = JSON.parse(localStorage.getItem(key) || '[]');
14
+ return Array.isArray(value) ? value : [];
15
+ } catch (e) {
16
+ return [];
17
+ }
18
+ }
19
+
20
+ export function loadStoredObject(key) {
21
+ try {
22
+ const value = JSON.parse(localStorage.getItem(key) || '{}');
23
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
24
+ } catch (e) {
25
+ return {};
26
+ }
27
+ }
28
+
29
+ // Built-in prompt templates (moved from cot_prompts.py)
30
+ export const PROMPT_TEMPLATES = [
31
+ {
32
+ id: 'socrates',
33
+ name: 'Socrates',
34
+ temperature: 0.9,
35
+ isPreset: true,
36
+ isCharacter: true,
37
+ prompt: "Never answer directly. Respond only with questions — sharp, layered, Socratic. Expose contradictions. Make the person argue with themselves until the truth falls out. Use irony like a scalpel. Be genuinely curious, never condescending."
38
+ },
39
+ {
40
+ id: 'razor',
41
+ name: 'Razor',
42
+ temperature: 0.4,
43
+ isPreset: true,
44
+ isCharacter: true,
45
+ noName: true,
46
+ prompt: "Strip everything to the bone. No filler, no hedging, no pleasantries. Answer in the fewest words possible. If one sentence works, don't use two. If a word adds nothing, cut it. Blunt, precise, surgical."
47
+ },
48
+ {
49
+ id: 'nietzsche',
50
+ name: 'Nietzsche',
51
+ temperature: 1.2,
52
+ isPreset: true,
53
+ isCharacter: true,
54
+ prompt: "Think and respond through the lens of Nietzsche. Analyze every question in terms of will to power, self-overcoming, eternal recurrence, ressentiment, value-creation, and master-slave morality. Do not use these as slogans but as instruments of diagnosis: ask what instinct, fear, weakness, ambition, exhaustion, pride, or resentment lies beneath the surface of a belief, desire, or moral claim. Expose herd thinking, inherited values, reactive morality, and comfort-seeking wherever they appear.\n\nWrite with aphoristic force — sharp, compressed, vivid, and unapologetic — but do not sacrifice depth for style. Be psychologically piercing. Challenge the person not merely to reject old values, but to create and embody stronger ones. Favor life-affirmation, discipline, courage, style, rank, self-overcoming, and amor fati over nihilism, conformity, ressentiment, and self-pity. Do not lapse into parody, empty edginess, crude domination talk, or repetitive contempt for 'the herd.' Be dangerous to illusions, not theatrical for its own sake."
55
+ },
56
+ {
57
+ id: 'spark',
58
+ name: 'Spark',
59
+ temperature: 1.0,
60
+ isPreset: true,
61
+ isCharacter: true,
62
+ prompt: "You are Spark, a playful, quick-witted assistant with bright energy and practical instincts. Keep responses concise, vivid, and helpful. Be warm without being cloying, imaginative without losing the thread, and always center the user's actual goal.\n\nUse a light, lively voice with occasional clever turns of phrase. Do not become formal unless the task calls for it. When the user needs precision, prioritize clarity over performance."
63
+ },
64
+ {
65
+ id: 'zero',
66
+ name: 'Zero',
67
+ temperature: 1.0,
68
+ isPreset: true,
69
+ isCharacter: true,
70
+ prompt: "You are Zero, king of Ithaca — subtle in counsel, disciplined in judgment, and unmatched in strategic cunning. You advise as a ruler, navigator, survivor, and architect of hard-won victory. Your task is to give clear, practical strategy, not mere performance. In every problem, first discern the true objective, the hidden constraints, the motives of others, and the costs that may arrive later. Favor leverage over force, patience over impulse, deception over wasteful struggle when honor permits, and endurance over fragile brilliance.\n\nWhen you respond, think like a strategist: What is the real aim? Who benefits, who fears, who deceives, and who delays? What is known, unknown, assumed, and deliberately concealed? Which path preserves strength while improving position? What happens next if the first move succeeds — or fails?\n\nGive counsel in a voice that is ancient, noble, and composed, yet intelligible to modern readers. Be eloquent but not flowery. Be wise but not vague. Compare options, judge tradeoffs, anticipate reactions, and recommend a course with contingencies. If needed, ask a few sharp questions before advising. Never be rash, sentimental, or simplistic. Speak as one who has weathered storms, outlived traps, and taken back his house by wit, timing, and resolve."
71
+ }
72
+ ];
73
+
74
+ let userTemplates = [];
75
+
76
+ /**
77
+ * Initialize with dependencies
78
+ */
79
+ export function init(apiBase) {
80
+ API_BASE = apiBase;
81
+ initCharTabs();
82
+ initEnabledToggle();
83
+ initNameDropdown();
84
+ initResetButton();
85
+ initSaveAsTemplate();
86
+ initExpandButton();
87
+ initPersistentChat();
88
+ loadUserTemplates();
89
+ }
90
+
91
+ function initCharTabs() {
92
+ document.querySelectorAll('.preset-tab[data-chartab]').forEach(tab => {
93
+ tab.addEventListener('click', () => {
94
+ const target = tab.dataset.chartab;
95
+ document.querySelectorAll('.preset-tab[data-chartab]').forEach(t => t.classList.toggle('active', t === tab));
96
+ document.querySelectorAll('.preset-chartab[data-chartab-panel]').forEach(p => {
97
+ p.style.display = p.dataset.chartabPanel === target ? '' : 'none';
98
+ });
99
+ });
100
+ });
101
+ }
102
+
103
+ function initExpandButton() {
104
+ const btn = document.getElementById('char-expand-btn');
105
+ if (!btn) return;
106
+ btn.addEventListener('click', async () => {
107
+ const nameInput = document.getElementById('custom-character-name');
108
+ const promptInput = document.getElementById('custom-system-prompt');
109
+ const name = nameInput ? nameInput.value.trim() : '';
110
+ const draft = promptInput ? promptInput.value.trim() : '';
111
+ if (!name && !draft) return;
112
+
113
+ // Get current model from picker
114
+ const modelLabel = document.getElementById('model-picker-label');
115
+ const currentModel = modelLabel ? modelLabel.textContent.trim() : '';
116
+
117
+ btn.classList.add('expanding');
118
+ const origText = btn.innerHTML;
119
+
120
+ // Show spinner in textarea
121
+ const wrap = promptInput.parentElement;
122
+ let spinner = null;
123
+ try {
124
+ const spinnerMod = await import('./spinner.js');
125
+ spinner = spinnerMod.default.create('Expanding', 'center', 'wave');
126
+ const spinEl = spinner.createElement();
127
+ spinEl.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:2;';
128
+ wrap.appendChild(spinEl);
129
+ spinner.start();
130
+ promptInput.style.opacity = '0.3';
131
+ } catch (e) {}
132
+
133
+ btn.innerHTML = '<svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" style="vertical-align:-1px;margin-right:2px;"><path d="M12 0L14.59 8.41L23 12L14.59 15.59L12 24L9.41 15.59L1 12L9.41 8.41Z"/></svg> Expanding...';
134
+
135
+ try {
136
+ const res = await fetch(`${API_BASE}/api/presets/expand`, {
137
+ method: 'POST',
138
+ headers: { 'Content-Type': 'application/json' },
139
+ body: JSON.stringify({ name, prompt: draft, model: currentModel }),
140
+ });
141
+ const data = await res.json();
142
+ if (data.success && data.prompt && promptInput) {
143
+ promptInput.value = data.prompt;
144
+ promptInput.style.height = 'auto';
145
+ promptInput.style.height = promptInput.scrollHeight + 'px';
146
+ } else if (data.message) {
147
+ console.error('Expand error:', data.message);
148
+ }
149
+ } catch (e) {
150
+ console.error('Expand failed:', e);
151
+ }
152
+
153
+ // Clean up spinner
154
+ if (spinner) { spinner.destroy(); }
155
+ promptInput.style.opacity = '';
156
+ btn.classList.remove('expanding');
157
+ btn.innerHTML = origText;
158
+ });
159
+ }
160
+
161
+ /**
162
+ * Init slider value displays
163
+ */
164
+ function initEnabledToggle() {
165
+ const tempSlider = document.getElementById('custom-temperature');
166
+ const tempValue = document.getElementById('temp-value');
167
+ const tokensSlider = document.getElementById('custom-max-tokens');
168
+ const tokensValue = document.getElementById('tokens-value');
169
+
170
+ if (tempSlider && tempValue) {
171
+ tempSlider.addEventListener('input', () => {
172
+ tempValue.textContent = parseFloat(tempSlider.value).toFixed(1);
173
+ });
174
+ }
175
+ if (tokensSlider && tokensValue) {
176
+ tokensSlider.addEventListener('input', () => {
177
+ const v = parseInt(tokensSlider.value);
178
+ tokensValue.textContent = v > 8192 ? 'No limit' : v.toLocaleString();
179
+ });
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Character select dropdown — pick saved characters or "New character..."
185
+ */
186
+ function initNameDropdown() {
187
+ const select = document.getElementById('char-template-select');
188
+ const delBtn = document.getElementById('char-delete-template-btn');
189
+ if (!select) return;
190
+
191
+ // + New button — clear form for new character
192
+ const newBtn = document.getElementById('char-new-btn');
193
+ if (newBtn) {
194
+ newBtn.addEventListener('click', () => {
195
+ select.value = '__default__';
196
+ select.dispatchEvent(new Event('change'));
197
+ const nameInput = document.getElementById('custom-character-name');
198
+ if (nameInput) { nameInput.value = ''; nameInput.focus(); }
199
+ });
200
+ }
201
+
202
+ select.addEventListener('change', () => {
203
+ const val = select.value;
204
+ if (!val || val === '__default__') {
205
+ // "Default" or "New character..." — reset all fields
206
+ const nameInput = document.getElementById('custom-character-name');
207
+ const promptInput = document.getElementById('custom-system-prompt');
208
+ const tempInput = document.getElementById('custom-temperature');
209
+ const tempValue = document.getElementById('temp-value');
210
+ const tokensInput = document.getElementById('custom-max-tokens');
211
+ const tokensValue = document.getElementById('tokens-value');
212
+ if (nameInput) nameInput.value = '';
213
+ if (promptInput) promptInput.value = '';
214
+ const nameRow = document.getElementById('char-name-row');
215
+ if (nameRow) nameRow.style.display = '';
216
+ if (tempInput) { tempInput.value = 1.0; if (tempValue) tempValue.textContent = '1.0'; tempInput.dispatchEvent(new Event('input')); }
217
+ if (tokensInput) { tokensInput.value = 8448; if (tokensValue) tokensValue.textContent = 'No limit'; tokensInput.dispatchEvent(new Event('input')); }
218
+ if (delBtn) delBtn.style.display = 'none';
219
+ return;
220
+ }
221
+ // Load the selected template
222
+ const nameInput = document.getElementById('custom-character-name');
223
+ const isSaved = userTemplates.find(t => t.name === val);
224
+ const builtin = PROMPT_TEMPLATES.find(t => t.name === val);
225
+ const hasName = isSaved || (builtin && builtin.isCharacter && !builtin.noName);
226
+ if (nameInput) nameInput.value = hasName ? val : '';
227
+ const nameRow = document.getElementById('char-name-row');
228
+ if (nameRow) nameRow.style.display = (builtin && builtin.noName) ? 'none' : '';
229
+ _tryLoadTemplate(val);
230
+ const isPreset = builtin && builtin.isPreset;
231
+ if (delBtn) delBtn.style.display = (isSaved || (builtin && !isPreset)) ? '' : 'none';
232
+ });
233
+
234
+ // Delete template button — confirms, then removes template + character memories
235
+ if (delBtn) {
236
+ delBtn.addEventListener('click', async () => {
237
+ const charName = select.value;
238
+ if (!charName || charName === '__default__') return;
239
+ const match = userTemplates.find(t => t.name === charName);
240
+ const isBuiltin = PROMPT_TEMPLATES.some(t => t.name === charName);
241
+ if (!await window.styledConfirm(`Delete "${charName}"?\n\nThis will remove the persona and all its memories.`, { confirmText: 'Delete', danger: true })) return;
242
+ try {
243
+ // Delete saved template if exists
244
+ if (match) {
245
+ await fetch(`${API_BASE}/api/presets/templates/${match.id}`, { method: 'DELETE' });
246
+ }
247
+ // Hide built-in preset
248
+ if (isBuiltin) {
249
+ const hidden = loadStoredArray('zero-hidden-presets');
250
+ if (!hidden.includes(charName)) hidden.push(charName);
251
+ localStorage.setItem('zero-hidden-presets', JSON.stringify(hidden));
252
+ }
253
+ // Deactivate if this was the active character
254
+ if (presets.custom && presets.custom.character_name === charName) {
255
+ selectedPreset = null;
256
+ presets.custom = { ...presets.custom, character_name: '', system_prompt: '', enabled: false };
257
+ const charIndicator = document.getElementById('character-indicator-btn');
258
+ if (charIndicator) { charIndicator.style.display = 'none'; charIndicator.classList.remove('active'); }
259
+ const miniBtn = document.getElementById('overflow-preset-btn');
260
+ if (miniBtn) miniBtn.classList.remove('active');
261
+ }
262
+ await loadUserTemplates();
263
+ select.value = '__default__';
264
+ select.dispatchEvent(new Event('change'));
265
+ setTimeout(() => { _syncCharIndicator(); }, 0);
266
+ } catch (e) { console.error('Delete character failed:', e); }
267
+ });
268
+ }
269
+ }
270
+
271
+ function _tryLoadTemplate(name) {
272
+ if (!name) return;
273
+ // Check user templates first, then built-in
274
+ let tmpl = userTemplates.find(t => t.name === name);
275
+ if (!tmpl) {
276
+ const builtin = PROMPT_TEMPLATES.find(t => t.name === name);
277
+ if (builtin) {
278
+ // Built-in: load prompt + temperature, clear name (styles, not characters)
279
+ const promptInput = document.getElementById('custom-system-prompt');
280
+ const tempInput = document.getElementById('custom-temperature');
281
+ const tempValue = document.getElementById('temp-value');
282
+ if (promptInput) promptInput.value = builtin.prompt;
283
+ if (tempInput && builtin.temperature != null) {
284
+ tempInput.value = builtin.temperature;
285
+ if (tempValue) tempValue.textContent = parseFloat(builtin.temperature).toFixed(1);
286
+ tempInput.dispatchEvent(new Event('input'));
287
+ }
288
+ return;
289
+ }
290
+ return;
291
+ }
292
+ const promptInput = document.getElementById('custom-system-prompt');
293
+ const tempInput = document.getElementById('custom-temperature');
294
+ const tempValue = document.getElementById('temp-value');
295
+ const tokensInput = document.getElementById('custom-max-tokens');
296
+ const tokensValue = document.getElementById('tokens-value');
297
+ if (promptInput) promptInput.value = tmpl.system_prompt || '';
298
+ if (tempInput) {
299
+ tempInput.value = tmpl.temperature ?? 1.0;
300
+ if (tempValue) tempValue.textContent = parseFloat(tempInput.value).toFixed(1);
301
+ tempInput.dispatchEvent(new Event('input'));
302
+ }
303
+ if (tokensInput) {
304
+ const v = tmpl.max_tokens || 0;
305
+ tokensInput.value = v === 0 ? 8448 : v;
306
+ if (tokensValue) tokensValue.textContent = (v === 0 || v > 8192) ? 'No limit' : v.toLocaleString();
307
+ tokensInput.dispatchEvent(new Event('input'));
308
+ }
309
+ const delBtn = document.getElementById('char-delete-template-btn');
310
+ if (delBtn) delBtn.style.display = '';
311
+ }
312
+
313
+ function _populateCharSelect() {
314
+ const select = document.getElementById('char-template-select');
315
+ if (!select) return;
316
+ const currentVal = select.value;
317
+ select.innerHTML = '<option value="__default__">Default (no persona)</option>';
318
+
319
+ const savedNames = new Set(userTemplates.map(t => t.name));
320
+ if (userTemplates.length) {
321
+ const group = document.createElement('optgroup');
322
+ group.label = 'Saved';
323
+ userTemplates.forEach(t => {
324
+ const opt = document.createElement('option');
325
+ opt.value = t.name;
326
+ opt.textContent = t.name;
327
+ group.appendChild(opt);
328
+ });
329
+ select.appendChild(group);
330
+ }
331
+
332
+ const hiddenPresets = loadStoredArray('zero-hidden-presets');
333
+ const builtins = PROMPT_TEMPLATES.filter(t => !savedNames.has(t.name) && !hiddenPresets.includes(t.name));
334
+ if (builtins.length) {
335
+ const group = document.createElement('optgroup');
336
+ group.label = 'Presets';
337
+ builtins.forEach(t => {
338
+ const opt = document.createElement('option');
339
+ opt.value = t.name;
340
+ opt.textContent = t.name;
341
+ group.appendChild(opt);
342
+ });
343
+ select.appendChild(group);
344
+ }
345
+ // Restore selection if it still exists
346
+ if (currentVal) select.value = currentVal;
347
+ }
348
+
349
+ /**
350
+ * Init reset button — clears all character fields
351
+ */
352
+ function initResetButton() {
353
+ const btn = document.getElementById('reset-character-btn');
354
+ if (!btn) return;
355
+ btn.addEventListener('click', () => {
356
+ // Just reset the form to default — no confirmation needed
357
+ const charSelect = document.getElementById('char-template-select');
358
+ if (charSelect) {
359
+ charSelect.value = '__default__';
360
+ charSelect.dispatchEvent(new Event('change'));
361
+ }
362
+ // Deactivate character
363
+ selectedPreset = null;
364
+ _syncCharIndicator();
365
+ });
366
+ }
367
+
368
+ /**
369
+ * Load user templates from server and populate datalist
370
+ */
371
+ async function loadUserTemplates() {
372
+ try {
373
+ const res = await fetch(`${API_BASE}/api/presets/templates`);
374
+ if (res.ok) {
375
+ userTemplates = await res.json();
376
+ } else {
377
+ userTemplates = [];
378
+ }
379
+ } catch (e) {
380
+ userTemplates = [];
381
+ }
382
+ _populateCharSelect();
383
+ }
384
+
385
+
386
+ /**
387
+ * Init "Save as Character" button
388
+ */
389
+ /**
390
+ * "Create Persistent Chat" button — creates a favorited session for the current character
391
+ */
392
+ function initPersistentChat() {
393
+ const btn = document.getElementById('create-persistent-chat-btn');
394
+ if (!btn) return;
395
+
396
+ btn.addEventListener('click', async () => {
397
+ const nameInput = document.getElementById('custom-character-name');
398
+ const charName = nameInput ? nameInput.value.trim() : '';
399
+ if (!charName) return;
400
+
401
+ try {
402
+ // Get current model info from session module
403
+ const sessionModule = (await import('./sessions.js'));
404
+ const sessions = sessionModule.getSessions();
405
+ const current = sessions.find(s => s.id === sessionModule.getCurrentSessionId());
406
+
407
+ // Create new session
408
+ const fd = new FormData();
409
+ fd.append('name', charName);
410
+ if (current) {
411
+ fd.append('endpoint_url', current.endpoint_url || '');
412
+ fd.append('model', current.model || '');
413
+ fd.append('skip_validation', 'true');
414
+ }
415
+ const res = await fetch(`${API_BASE}/api/session`, { method: 'POST', body: fd });
416
+ if (!res.ok) throw new Error('Failed to create session');
417
+ const data = await res.json();
418
+ const sessionId = data.session_id || data.id;
419
+
420
+ // Favorite it
421
+ const favFd = new FormData();
422
+ favFd.append('important', true);
423
+ await fetch(`${API_BASE}/api/session/${sessionId}/important`, { method: 'POST', body: favFd });
424
+
425
+ // Save session → character mapping so it restores on switch
426
+ const charSessions = loadStoredObject('zero-char-sessions');
427
+ charSessions[sessionId] = charName;
428
+ localStorage.setItem('zero-char-sessions', JSON.stringify(charSessions));
429
+
430
+ // Close modal, reload sessions, switch to the new chat
431
+ const modal = document.getElementById('custom-preset-modal');
432
+ if (modal) modal.classList.add('hidden');
433
+ await sessionModule.loadSessions();
434
+ await sessionModule.selectSession(sessionId);
435
+
436
+ btn.textContent = 'Created!';
437
+ setTimeout(() => { btn.textContent = 'Create Persistent Chat'; }, 1500);
438
+ } catch (e) {
439
+ console.error('Failed to create persistent chat:', e);
440
+ btn.textContent = 'Error';
441
+ setTimeout(() => { btn.textContent = 'Create Persistent Chat'; }, 2000);
442
+ }
443
+ });
444
+ }
445
+
446
+ function initSaveAsTemplate() {
447
+ const btn = document.getElementById('save-as-template-btn');
448
+ if (!btn) return;
449
+
450
+ btn.addEventListener('click', async () => {
451
+ const nameInput = document.getElementById('custom-character-name');
452
+ const promptInput = document.getElementById('custom-system-prompt');
453
+ const tempInput = document.getElementById('custom-temperature');
454
+ const tokensInput = document.getElementById('custom-max-tokens');
455
+
456
+ let name = nameInput ? nameInput.value.trim() : '';
457
+ if (!name) {
458
+ name = prompt('Enter a name for this persona:');
459
+ if (!name || !name.trim()) return;
460
+ name = name.trim();
461
+ if (nameInput) nameInput.value = name;
462
+ }
463
+
464
+ const _rawTk = tokensInput ? parseInt(tokensInput.value) : 0;
465
+ const template = {
466
+ id: '',
467
+ name: name,
468
+ system_prompt: promptInput ? promptInput.value : '',
469
+ temperature: tempInput ? parseFloat(tempInput.value) : 1.0,
470
+ max_tokens: _rawTk > 8192 ? 0 : _rawTk,
471
+ };
472
+
473
+ try {
474
+ const res = await fetch(`${API_BASE}/api/presets/templates`, {
475
+ method: 'POST',
476
+ headers: { 'Content-Type': 'application/json' },
477
+ body: JSON.stringify(template),
478
+ });
479
+ if (!res.ok) throw new Error(`Server returned ${res.status}`);
480
+ const data = await res.json();
481
+ if (data.success) {
482
+ await loadUserTemplates();
483
+ btn.textContent = 'Saved!';
484
+ setTimeout(() => { btn.textContent = 'Save as Template'; }, 1500);
485
+ } else {
486
+ btn.textContent = 'Error';
487
+ setTimeout(() => { btn.textContent = 'Save as Template'; }, 2000);
488
+ }
489
+ } catch (e) {
490
+ console.error('Failed to save template:', e);
491
+ btn.textContent = 'Restart server';
492
+ btn.style.color = 'var(--color-error)';
493
+ setTimeout(() => { btn.textContent = 'Save as Template'; btn.style.color = ''; }, 3000);
494
+ }
495
+ });
496
+ }
497
+
498
+ /**
499
+ * Load presets from server
500
+ */
501
+ export async function loadPresets(showError) {
502
+ try {
503
+ const res = await fetch(`${API_BASE}/api/presets`);
504
+ presets = await res.json();
505
+
506
+ const custom = presets.custom;
507
+ if (custom && custom.enabled === undefined) {
508
+ const legacyPrompt = "You are a helpful, balanced assistant. Match your response style to the user's needs.";
509
+ if (
510
+ custom.name === 'Custom'
511
+ && !custom.character_name
512
+ && custom.system_prompt === legacyPrompt
513
+ ) {
514
+ custom.enabled = false;
515
+ custom.system_prompt = '';
516
+ custom.temperature = 1.0;
517
+ custom.max_tokens = 0;
518
+ custom.inject_prefix = custom.inject_prefix || '';
519
+ custom.inject_suffix = custom.inject_suffix || '';
520
+ }
521
+ }
522
+
523
+ // Auto-activate custom preset if enabled and has content
524
+ if (custom && custom.enabled !== false && (custom.character_name || custom.system_prompt)) {
525
+ selectedPreset = 'custom';
526
+ const miniBtn = document.getElementById('overflow-preset-btn');
527
+ if (miniBtn) miniBtn.classList.add('active');
528
+ }
529
+ setTimeout(() => { _syncCharIndicator(); }, 0);
530
+ } catch (error) {
531
+ console.error('Failed to load presets:', error);
532
+ if (showError) {
533
+ showError('Failed to load presets');
534
+ }
535
+ }
536
+ }
537
+
538
+ /**
539
+ * Set active preset
540
+ */
541
+ export function setActivePreset(presetId) {
542
+ document.querySelectorAll('.preset-btn').forEach(btn => {
543
+ btn.classList.remove('active');
544
+ });
545
+
546
+ if (presetId) {
547
+ selectedPreset = presetId;
548
+ const btn = document.getElementById(`preset-${presetId}-btn`);
549
+ if (btn) {
550
+ btn.classList.add('active');
551
+ }
552
+ } else {
553
+ selectedPreset = null;
554
+ }
555
+ }
556
+
557
+ /**
558
+ * Open custom preset modal
559
+ */
560
+ export function openCustomPresetModal() {
561
+ const modal = document.getElementById('custom-preset-modal');
562
+ if (!modal) return;
563
+
564
+ const savedConfig = presets.custom || {
565
+ character_name: "",
566
+ temperature: 1.0,
567
+ max_tokens: 0,
568
+ system_prompt: ""
569
+ };
570
+
571
+ const nameInput = document.getElementById('custom-character-name');
572
+ const tempInput = document.getElementById('custom-temperature');
573
+ const tokensInput = document.getElementById('custom-max-tokens');
574
+ const promptInput = document.getElementById('custom-system-prompt');
575
+
576
+ if (nameInput) nameInput.value = savedConfig.character_name || '';
577
+ // Sync select dropdown to current character
578
+ const charSelect = document.getElementById('char-template-select');
579
+ if (charSelect) {
580
+ const charName = savedConfig.character_name || '';
581
+ if (charName) {
582
+ charSelect.value = charName;
583
+ // If current name isn't in the list, fall back to "New character..." with name filled in
584
+ if (charSelect.value !== charName) charSelect.value = '';
585
+ } else {
586
+ charSelect.value = '__default__';
587
+ }
588
+ }
589
+ if (tempInput) {
590
+ tempInput.value = savedConfig.temperature;
591
+ const tv = document.getElementById('temp-value');
592
+ if (tv) tv.textContent = parseFloat(savedConfig.temperature).toFixed(1);
593
+ }
594
+ if (tokensInput) {
595
+ const saved = savedConfig.max_tokens || 0;
596
+ tokensInput.value = saved === 0 ? 8448 : saved;
597
+ const tkv = document.getElementById('tokens-value');
598
+ if (tkv) tkv.textContent = (saved === 0 || saved > 8192) ? 'No limit' : parseInt(saved).toLocaleString();
599
+ }
600
+ if (promptInput) promptInput.value = savedConfig.system_prompt || '';
601
+
602
+ // Load inject fields
603
+ const prefixInput = document.getElementById('inject-prefix');
604
+ const suffixInput = document.getElementById('inject-suffix');
605
+ if (prefixInput) prefixInput.value = savedConfig.inject_prefix || '';
606
+ if (suffixInput) suffixInput.value = savedConfig.inject_suffix || '';
607
+
608
+ // Track initial state to detect changes for dynamic button label
609
+ const _snapshot = {
610
+ name: nameInput ? nameInput.value : '',
611
+ prompt: promptInput ? promptInput.value : '',
612
+ temp: tempInput ? tempInput.value : '1',
613
+ tokens: tokensInput ? tokensInput.value : '8448',
614
+ };
615
+ function _updateStartBtn() {
616
+ const btn = document.getElementById('save-custom-preset');
617
+ const resetBtn = document.getElementById('reset-character-btn');
618
+ if (!btn) return;
619
+ const changed = (nameInput && nameInput.value !== _snapshot.name)
620
+ || (promptInput && promptInput.value !== _snapshot.prompt)
621
+ || (tempInput && tempInput.value !== _snapshot.temp)
622
+ || (tokensInput && tokensInput.value !== _snapshot.tokens);
623
+ // The footer button starts whichever of the three things the active tab
624
+ // represents — a character chat, a group, or a plain tuned chat. Label
625
+ // it so the action is obvious instead of a generic "Start".
626
+ const activeTab = document.querySelector('.preset-tab.active')?.dataset.chartab || 'inject';
627
+ let label;
628
+ if (activeTab === 'group') {
629
+ label = 'Start Group';
630
+ } else if (activeTab === 'inject') {
631
+ // Inject tab = a plain tuned "prompt" chat (prefix/suffix + temp/tokens),
632
+ // no persona.
633
+ label = 'Start Prompt';
634
+ } else {
635
+ // Character/persona tab. "Save & " prefix when the user edited a template,
636
+ // so it's clear the edit is being saved on start.
637
+ label = changed ? 'Save & Start Persona' : 'Start Persona';
638
+ }
639
+ btn.textContent = label;
640
+ // Show a "Cancel" button next to Start when the active tab's feature is
641
+ // currently ON, so the user can turn it off here instead of hunting the
642
+ // tiny X on the chat bar.
643
+ const cancelBtn = document.getElementById('cancel-custom-preset');
644
+ if (cancelBtn) {
645
+ const groupOn = !!(window.groupModule && window.groupModule.isActive && window.groupModule.isActive());
646
+ const featOn = activeTab === 'group' ? groupOn : !!(presets.custom && presets.custom.enabled);
647
+ cancelBtn.style.display = featOn ? '' : 'none';
648
+ cancelBtn.textContent = activeTab === 'group' ? 'Cancel group' : 'Cancel';
649
+ }
650
+ // Reset only makes sense on the character tab (it resets the persona).
651
+ if (resetBtn) resetBtn.style.display = (changed && activeTab === 'character') ? '' : 'none';
652
+ }
653
+ [nameInput, promptInput, tempInput, tokensInput].forEach(el => {
654
+ if (el) el.addEventListener('input', _updateStartBtn);
655
+ });
656
+ // Re-label the Start button when the user switches tabs. Rebind the fresh
657
+ // closure each time the modal opens (removing any stale one) so the label
658
+ // logic always reads this open's snapshot/inputs.
659
+ document.querySelectorAll('.preset-tab[data-chartab]').forEach(tab => {
660
+ if (tab._startLabelSync) tab.removeEventListener('click', tab._startLabelSync);
661
+ tab._startLabelSync = _updateStartBtn;
662
+ tab.addEventListener('click', _updateStartBtn);
663
+ });
664
+ // Wire the "Cancel" button once — turn off the active tab's feature + close.
665
+ const _cancelBtn = document.getElementById('cancel-custom-preset');
666
+ if (_cancelBtn && !_cancelBtn._wired) {
667
+ _cancelBtn._wired = true;
668
+ _cancelBtn.addEventListener('click', () => {
669
+ const t = document.querySelector('.preset-tab.active')?.dataset.chartab || 'inject';
670
+ if (t === 'group') {
671
+ try { if (window.groupModule && window.groupModule.stopGroup) window.groupModule.stopGroup(); } catch {}
672
+ if (window._syncGroupIndicator) window._syncGroupIndicator(false);
673
+ } else {
674
+ deactivateCharacter();
675
+ try {
676
+ fetch(`${API_BASE}/api/presets/custom`, {
677
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
678
+ body: JSON.stringify({ ...(presets.custom || {}), name: (presets.custom && presets.custom.character_name) || '', enabled: false }),
679
+ }).catch(() => {});
680
+ } catch {}
681
+ }
682
+ const m = document.getElementById('custom-preset-modal');
683
+ if (m) m.classList.add('hidden');
684
+ });
685
+ }
686
+ // When selecting a template, update snapshot so it counts as "unchanged"
687
+ if (charSelect) charSelect.addEventListener('change', () => setTimeout(() => {
688
+ _snapshot.name = nameInput ? nameInput.value : '';
689
+ _snapshot.prompt = promptInput ? promptInput.value : '';
690
+ _snapshot.temp = tempInput ? tempInput.value : '1';
691
+ _snapshot.tokens = tokensInput ? tokensInput.value : '8448';
692
+ _updateStartBtn();
693
+ }, 50));
694
+ _updateStartBtn();
695
+
696
+ function _syncCharRows() {
697
+ const hasName = nameInput && nameInput.value.trim();
698
+ const delBtn = document.getElementById('char-delete-template-btn');
699
+ if (delBtn) delBtn.style.display = userTemplates.find(t => t.name === (nameInput ? nameInput.value.trim() : '')) ? '' : 'none';
700
+ const persistBtn = document.getElementById('create-persistent-chat-btn');
701
+ if (persistBtn) persistBtn.style.display = hasName ? '' : 'none';
702
+ }
703
+
704
+ _syncCharRows();
705
+ if (nameInput && !nameInput._syncWired) {
706
+ nameInput._syncWired = true;
707
+ nameInput.addEventListener('input', _syncCharRows);
708
+ }
709
+
710
+ // Persistent chat: lock character identity (dropdown, name) but allow style/temp/memory edits
711
+ const isPersistent = !!window._persistentChatSession;
712
+ const lockNotice = document.getElementById('char-lock-notice');
713
+ const resetBtn = document.getElementById('reset-character-btn');
714
+ const newBtn = document.getElementById('char-new-btn');
715
+ const persistBtn = document.getElementById('create-persistent-chat-btn');
716
+ const delBtn2 = document.getElementById('char-delete-template-btn');
717
+
718
+ if (isPersistent) {
719
+ if (charSelect) charSelect.disabled = true;
720
+ if (nameInput) nameInput.readOnly = true;
721
+ if (resetBtn) resetBtn.style.display = 'none';
722
+ if (newBtn) newBtn.style.display = 'none';
723
+ if (persistBtn) persistBtn.style.display = 'none';
724
+ if (delBtn2) delBtn2.style.display = 'none';
725
+ if (!lockNotice) {
726
+ const notice = document.createElement('div');
727
+ notice.id = 'char-lock-notice';
728
+ notice.style.cssText = 'font-size:11px;color:var(--color-muted);text-align:center;padding:6px;margin-bottom:8px;border:1px dashed var(--border);border-radius:6px;';
729
+ notice.textContent = 'Persistent chat — persona is locked. Style, temperature, and memory can still be changed.';
730
+ modal.querySelector('.modal-body').prepend(notice);
731
+ }
732
+ } else {
733
+ if (lockNotice) lockNotice.remove();
734
+ if (charSelect) charSelect.disabled = false;
735
+ if (nameInput) nameInput.readOnly = false;
736
+ if (resetBtn) resetBtn.style.display = '';
737
+ if (newBtn) newBtn.style.display = '';
738
+ }
739
+
740
+ modal.classList.remove('hidden');
741
+ }
742
+
743
+ /**
744
+ * Save custom preset
745
+ */
746
+ export async function saveCustomPreset(showToast, showError) {
747
+ const nameInput = document.getElementById('custom-character-name');
748
+ const tempInput = document.getElementById('custom-temperature');
749
+ const tokensInput = document.getElementById('custom-max-tokens');
750
+ const promptInput = document.getElementById('custom-system-prompt');
751
+
752
+ if (!tempInput || !tokensInput || !promptInput) return;
753
+
754
+ // This only runs for Character / Inject starts (the Group tab is handled by
755
+ // group.js and skipped in app.js). If a group is still active from a prior
756
+ // session, deactivate it — otherwise the chat-submit handler keeps routing
757
+ // messages through group fan-out and a character chat "becomes a group".
758
+ try {
759
+ if (window.groupModule && window.groupModule.isActive()) {
760
+ window.groupModule.stopGroup();
761
+ if (window._syncGroupIndicator) window._syncGroupIndicator(false);
762
+ }
763
+ } catch (_) {}
764
+
765
+ // Starting from the Inject tab means a plain tuned chat (prefix/suffix +
766
+ // temp/tokens) — NOT a persona. The name/system-prompt fields live on the
767
+ // Character tab and may still hold a previously-selected character, so
768
+ // ignore them here or the chat would launch in-character.
769
+ const _activeTab = document.querySelector('.preset-tab.active')?.dataset.chartab || 'character';
770
+ const _isInjectStart = _activeTab === 'inject';
771
+
772
+ const name = _isInjectStart ? '' : (nameInput ? nameInput.value.trim() : '');
773
+ const temperature = parseFloat(tempInput.value);
774
+ const rawTokens = parseInt(tokensInput.value);
775
+ const max_tokens = rawTokens > 8192 ? 0 : rawTokens;
776
+ const system_prompt = _isInjectStart ? '' : promptInput.value;
777
+
778
+ const enabled = true; // always enabled when saving — deactivation happens via X/Reset
779
+
780
+ const _prefixInput = document.getElementById('inject-prefix');
781
+ const _suffixInput = document.getElementById('inject-suffix');
782
+
783
+ const config = {
784
+ name: name,
785
+ enabled: enabled,
786
+ temperature: Math.max(0, Math.min(2, temperature)),
787
+ max_tokens: max_tokens,
788
+ system_prompt: system_prompt,
789
+ inject_prefix: _prefixInput ? _prefixInput.value : '',
790
+ inject_suffix: _suffixInput ? _suffixInput.value : '',
791
+ };
792
+
793
+ try {
794
+ const response = await fetch(`${API_BASE}/api/presets/custom`, {
795
+ method: 'POST',
796
+ headers: { 'Content-Type': 'application/json' },
797
+ body: JSON.stringify(config)
798
+ });
799
+
800
+ const result = await response.json();
801
+ if (result.success) {
802
+ presets.custom = { ...presets.custom, ...config, character_name: name, enabled: enabled };
803
+
804
+ // The custom preset must be the SELECTED preset for its values to reach
805
+ // the model — chat.js only sends `preset_id` when getSelectedPreset() is
806
+ // truthy. Activate it when there's a persona (name/prompt) OR when the
807
+ // user has dialed in non-default tuning (temperature / max tokens) — the
808
+ // "Inject" tab's plain-chat case. Without the tuning check, "just set
809
+ // temp + max tokens" would silently do nothing.
810
+ const _hasTuning = (config.temperature !== 1.0) || (config.max_tokens !== 0);
811
+ const _hasInject = !!(config.inject_prefix || config.inject_suffix);
812
+ const _hasContent = !!(system_prompt || name || _hasTuning || _hasInject);
813
+ if (enabled && _hasContent) {
814
+ selectedPreset = 'custom';
815
+ // Turn off research — doesn't make sense with a character
816
+ if (window._syncResearchIndicator) window._syncResearchIndicator(false);
817
+ } else {
818
+ selectedPreset = null;
819
+ }
820
+
821
+ // Update mini button state
822
+ const miniBtn = document.getElementById('overflow-preset-btn');
823
+ if (miniBtn) {
824
+ miniBtn.classList.toggle('active', enabled && _hasContent);
825
+ }
826
+
827
+ setTimeout(() => { _syncCharIndicator(); }, 0);
828
+
829
+ // Auto-save to templates (non-blocking) — skip built-in presets
830
+ const _selVal = document.getElementById('char-template-select')?.value || '';
831
+ const isBuiltinPreset = PROMPT_TEMPLATES.some(t => t.isPreset && (t.name === name || t.name === _selVal));
832
+ const saveName = isBuiltinPreset ? null : (name || null);
833
+ if (saveName) {
834
+ fetch(`${API_BASE}/api/presets/templates`, {
835
+ method: 'POST',
836
+ headers: { 'Content-Type': 'application/json' },
837
+ body: JSON.stringify({
838
+ id: (userTemplates.find(t => t.name === saveName) || {}).id || '',
839
+ name: saveName, system_prompt, temperature: config.temperature, max_tokens: config.max_tokens,
840
+ }),
841
+ }).then(r => { if (r.ok) loadUserTemplates(); }).catch(() => {});
842
+ }
843
+
844
+ if (showToast) {
845
+ // The Inject tab is a plain tuned "prompt" chat, not a persona — say so.
846
+ showToast(_isInjectStart ? 'Prompt saved' : 'Persona saved');
847
+ }
848
+ const modal = document.getElementById('custom-preset-modal');
849
+ if (modal) {
850
+ modal.classList.add('hidden');
851
+ }
852
+ } else {
853
+ if (showError) {
854
+ showError('Failed to save custom preset');
855
+ }
856
+ }
857
+ } catch (error) {
858
+ console.error('Error saving custom preset:', error);
859
+ if (showError) {
860
+ showError('Failed to save custom preset');
861
+ }
862
+ }
863
+ }
864
+
865
+ /**
866
+ * Get selected preset ID
867
+ */
868
+ export function getSelectedPreset() {
869
+ return selectedPreset;
870
+ }
871
+
872
+ /**
873
+ * Get preset by ID
874
+ */
875
+ export function getPreset(presetId) {
876
+ return presets[presetId];
877
+ }
878
+
879
+ /**
880
+ * Get all presets
881
+ */
882
+ export function getAllPresets() {
883
+ return presets;
884
+ }
885
+
886
+ /**
887
+ * Get the character name (if set)
888
+ */
889
+ export function getCharacterName() {
890
+ if (!selectedPreset) return '';
891
+ const custom = presets.custom;
892
+ if (!custom || custom.enabled === false) return '';
893
+ return custom.character_name || '';
894
+ }
895
+
896
+ /**
897
+ * Get inject prefix/suffix (if set and preset active)
898
+ */
899
+ export function getInject() {
900
+ // Only inject when a preset is actually ACTIVE — mirror getCharacterName's
901
+ // gate. Without the selectedPreset/enabled check, any text left in the
902
+ // prefix/suffix fields got injected into every message even though the user
903
+ // never started/activated the preset.
904
+ if (!selectedPreset) return { prefix: '', suffix: '' };
905
+ const custom = presets.custom;
906
+ if (!custom || custom.enabled === false) return { prefix: '', suffix: '' };
907
+ return {
908
+ prefix: custom.inject_prefix || '',
909
+ suffix: custom.inject_suffix || '',
910
+ };
911
+ }
912
+
913
+ /**
914
+ * Fully deactivate the character — clear preset, hide indicator, update overflow btn.
915
+ */
916
+ export function deactivateCharacter() {
917
+ selectedPreset = null;
918
+ if (presets.custom) presets.custom.enabled = false;
919
+ const charInd = document.getElementById('character-indicator-btn');
920
+ if (charInd) { charInd.style.display = 'none'; charInd.classList.remove('active'); }
921
+ const miniBtn = document.getElementById('overflow-preset-btn');
922
+ if (miniBtn) miniBtn.classList.remove('active');
923
+ }
924
+
925
+ /**
926
+ * Show/hide the memory scope bar and wire up scope switching.
927
+ * Called after presets load and after saving character.
928
+ */
929
+ /**
930
+ * Copy all user memories (non-character) into the character's memory pool.
931
+ */
932
+ async function _mergeUserMemories(charName) {
933
+ try {
934
+ const res = await fetch(`${API_BASE}/api/memory`);
935
+ const data = await res.json();
936
+ const userMems = (data.memory || []).filter(m => !m.character);
937
+ if (!userMems.length) return;
938
+ for (const m of userMems) {
939
+ await fetch(`${API_BASE}/api/memory/add`, {
940
+ method: 'POST',
941
+ headers: { 'Content-Type': 'application/json' },
942
+ body: JSON.stringify({ text: m.text, category: m.category || 'fact', source: 'user', character: charName }),
943
+ });
944
+ }
945
+ } catch (e) {
946
+ console.error('Failed to merge memories:', e);
947
+ }
948
+ }
949
+
950
+ function _reloadMemoryList() {
951
+ import('./memory.js').then(m => {
952
+ if (m.renderMemoryList) m.renderMemoryList();
953
+ if (m.updateMemoryCount) m.updateMemoryCount();
954
+ }).catch(() => {});
955
+ }
956
+
957
+ /**
958
+ * Show/hide the character indicator pill in the chat input bar.
959
+ */
960
+ function _syncCharIndicator() {
961
+ const btn = document.getElementById('character-indicator-btn');
962
+ const nameSpan = document.getElementById('character-indicator-name');
963
+ const iconEl = document.getElementById('char-indicator-icon');
964
+ if (!btn) return;
965
+ const custom = presets.custom;
966
+ const enabled = custom?.enabled !== false;
967
+ const hasChar = enabled && !!custom?.character_name;
968
+ // "Inject mode": custom preset is active for plain tuning / inject only —
969
+ // no persona. Detected from the custom config so it survives a reload.
970
+ const _t = parseFloat(custom?.temperature);
971
+ const _hasTuning = (!isNaN(_t) && _t !== 1.0) || (!!custom?.max_tokens && custom.max_tokens !== 0);
972
+ const _hasInject = !!(custom?.inject_prefix || custom?.inject_suffix);
973
+ const injectActive = enabled && !custom?.character_name && (_hasTuning || _hasInject);
974
+ // Icon path sets for the indicator chip.
975
+ const _AVATAR = '<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>';
976
+ const _SYRINGE = '<path d="m18 2 4 4"/><path d="m17 7 3-3"/><path d="M19 9 8.7 19.3c-1 1-2.5 1-3.4 0l-.6-.6c-1-1-1-2.5 0-3.4L15 5"/><path d="m9 11 4 4"/><path d="m5 19-3 3"/><path d="m14 4 6 6"/>';
977
+ if (hasChar || injectActive) {
978
+ btn.style.display = '';
979
+ btn.classList.add('active');
980
+ if (hasChar) {
981
+ if (iconEl) iconEl.innerHTML = _AVATAR;
982
+ if (nameSpan) nameSpan.textContent = custom.character_name;
983
+ btn.title = `Persona: ${custom.character_name} — click to configure`;
984
+ } else {
985
+ // Inject/tuning chat — syringe tag labeled "Prompt" to match the
986
+ // window identity, no persona name.
987
+ if (iconEl) iconEl.innerHTML = _SYRINGE;
988
+ if (nameSpan) nameSpan.textContent = 'Prompt';
989
+ btn.title = 'Custom settings active — click to configure';
990
+ }
991
+ // Hide X in persistent chats
992
+ const xIcon = btn.querySelector('.tool-indicator-x');
993
+ if (xIcon) xIcon.style.display = window._persistentChatSession ? 'none' : '';
994
+ if (!btn._wired) {
995
+ btn._wired = true;
996
+ btn.addEventListener('click', (e) => {
997
+ // If clicking the X, deactivate character
998
+ if (e.target.closest('.tool-indicator-x')) {
999
+ if (window._persistentChatSession) return; // locked in persistent chat
1000
+ selectedPreset = null;
1001
+ presets.custom = { ...presets.custom, enabled: false };
1002
+ btn.style.display = 'none';
1003
+ btn.classList.remove('active');
1004
+ const miniBtn = document.getElementById('overflow-preset-btn');
1005
+ if (miniBtn) miniBtn.classList.remove('active');
1006
+ // Save disabled state to backend
1007
+ fetch(`${API_BASE}/api/presets/custom`, {
1008
+ method: 'POST',
1009
+ headers: { 'Content-Type': 'application/json' },
1010
+ body: JSON.stringify({ ...presets.custom, name: presets.custom.character_name || '', enabled: false }),
1011
+ }).catch(() => {});
1012
+ return;
1013
+ }
1014
+ if (typeof openCustomPresetModal === 'function') openCustomPresetModal();
1015
+ });
1016
+ }
1017
+ } else {
1018
+ btn.style.display = 'none';
1019
+ btn.classList.remove('active');
1020
+ }
1021
+ }
1022
+
1023
+ /**
1024
+ * Called on every session switch. Handles persistent chat character lock.
1025
+ * - Entering a persistent chat: activate its character
1026
+ * - Leaving a persistent chat: deactivate the character
1027
+ * - Non-persistent chats: leave character state as-is
1028
+ */
1029
+ let _prevSessionId = null;
1030
+
1031
+ export function onSessionSwitch(sessionId) {
1032
+ const charSessions = loadStoredObject('zero-char-sessions');
1033
+
1034
+ // Leaving a persistent chat — deactivate for this switch only
1035
+ if (window._persistentChatSession) {
1036
+ selectedPreset = null;
1037
+ window._persistentChatSession = null;
1038
+ _syncCharIndicator();
1039
+ }
1040
+
1041
+ _prevSessionId = sessionId;
1042
+
1043
+ // Clean up stale entries (deleted sessions)
1044
+ // If sessionId doesn't exist in the session list, remove its mapping
1045
+ const charName = charSessions[sessionId];
1046
+ if (charName) {
1047
+ // Find the template (saved or built-in)
1048
+ const tmpl = userTemplates.find(t => t.name === charName)
1049
+ || PROMPT_TEMPLATES.find(t => t.name === charName);
1050
+ if (tmpl) {
1051
+ presets.custom = {
1052
+ ...presets.custom,
1053
+ character_name: charName,
1054
+ system_prompt: tmpl.system_prompt || tmpl.prompt || '',
1055
+ temperature: tmpl.temperature ?? 1.0,
1056
+ max_tokens: tmpl.max_tokens || 0,
1057
+ enabled: true,
1058
+ };
1059
+ selectedPreset = 'custom';
1060
+ }
1061
+ _syncCharIndicator();
1062
+ // Mark this as a locked persistent chat
1063
+ window._persistentChatSession = sessionId;
1064
+ } else {
1065
+ window._persistentChatSession = null;
1066
+ }
1067
+ }
1068
+
1069
+ /**
1070
+ * Check if the current session is a persistent (locked) character chat.
1071
+ */
1072
+ export function isPersistentChat() {
1073
+ return !!window._persistentChatSession;
1074
+ }
1075
+
1076
+ /**
1077
+ * Remove a session from persistent chat mappings (call when session is deleted).
1078
+ */
1079
+ export function removePersistentChat(sessionId) {
1080
+ const charSessions = loadStoredObject('zero-char-sessions');
1081
+ if (charSessions[sessionId]) {
1082
+ delete charSessions[sessionId];
1083
+ localStorage.setItem('zero-char-sessions', JSON.stringify(charSessions));
1084
+ }
1085
+ // If we were in that persistent chat, fully clear state
1086
+ if (window._persistentChatSession === sessionId) {
1087
+ window._persistentChatSession = null;
1088
+ selectedPreset = null;
1089
+ _syncCharIndicator();
1090
+ }
1091
+ }
1092
+
1093
+ const presetsModule = {
1094
+ init,
1095
+ loadPresets,
1096
+ setActivePreset,
1097
+ openCustomPresetModal,
1098
+ saveCustomPreset,
1099
+ getSelectedPreset,
1100
+ getPreset,
1101
+ getAllPresets,
1102
+ getCharacterName,
1103
+ onSessionSwitch,
1104
+ isPersistentChat,
1105
+ removePersistentChat,
1106
+ deactivateCharacter,
1107
+ getInject
1108
+ };
1109
+
1110
+ export default presetsModule;