glirastes 0.3.0 → 0.3.2

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 (310) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +154 -9
  3. package/codegen/package.json +5 -0
  4. package/dist/cjs/codegen/asset-sync.d.ts +20 -0
  5. package/dist/cjs/codegen/asset-sync.js +28 -0
  6. package/dist/cjs/codegen/coverage.d.ts +66 -0
  7. package/dist/cjs/codegen/coverage.js +408 -0
  8. package/dist/cjs/codegen/endpoint-scanner.d.ts +16 -0
  9. package/dist/cjs/codegen/endpoint-scanner.js +197 -0
  10. package/dist/cjs/codegen/format.d.ts +17 -0
  11. package/dist/cjs/codegen/format.js +34 -0
  12. package/dist/cjs/codegen/fs-utils.d.ts +45 -0
  13. package/dist/cjs/codegen/fs-utils.js +165 -0
  14. package/dist/cjs/codegen/index.d.ts +13 -0
  15. package/dist/cjs/codegen/index.js +33 -0
  16. package/dist/cjs/codegen/mcp-generator.d.ts +50 -0
  17. package/dist/cjs/codegen/mcp-generator.js +803 -0
  18. package/dist/cjs/codegen/module-generator.d.ts +11 -0
  19. package/dist/cjs/codegen/module-generator.js +353 -0
  20. package/dist/cjs/codegen/nestjs-scanner.d.ts +43 -0
  21. package/dist/cjs/codegen/nestjs-scanner.js +241 -0
  22. package/dist/cjs/codegen/output-recommender.d.ts +23 -0
  23. package/dist/cjs/codegen/output-recommender.js +33 -0
  24. package/dist/cjs/codegen/scaffolder.d.ts +36 -0
  25. package/dist/cjs/codegen/scaffolder.js +262 -0
  26. package/dist/cjs/codegen/skill-generator.d.ts +49 -0
  27. package/dist/cjs/codegen/skill-generator.js +967 -0
  28. package/dist/cjs/codegen/types.d.ts +52 -0
  29. package/dist/cjs/codegen/types.js +16 -0
  30. package/dist/cjs/codegen/ui-scanner.d.ts +24 -0
  31. package/dist/cjs/codegen/ui-scanner.js +202 -0
  32. package/dist/cjs/codegen/validator.d.ts +35 -0
  33. package/dist/cjs/codegen/validator.js +292 -0
  34. package/dist/cjs/index.d.ts +1 -0
  35. package/dist/cjs/index.js +23 -0
  36. package/dist/cjs/openapi/index.d.ts +78 -0
  37. package/dist/cjs/openapi/index.js +347 -0
  38. package/dist/cjs/package.json +1 -0
  39. package/dist/cjs/react/audio-worklet-processor.d.ts +16 -0
  40. package/dist/cjs/react/audio-worklet-processor.js +27 -0
  41. package/dist/cjs/react/bridges/rest-session-bridge.d.ts +113 -0
  42. package/dist/cjs/react/bridges/rest-session-bridge.js +209 -0
  43. package/dist/cjs/react/components/ai-chat-panel.d.ts +12 -0
  44. package/dist/cjs/react/components/ai-chat-panel.js +67 -0
  45. package/dist/cjs/react/components/ai-trigger-button.d.ts +2 -0
  46. package/dist/cjs/react/components/ai-trigger-button.js +239 -0
  47. package/dist/cjs/react/components/approval-card.d.ts +9 -0
  48. package/dist/cjs/react/components/approval-card.js +24 -0
  49. package/dist/cjs/react/components/bulk-approval-card.d.ts +9 -0
  50. package/dist/cjs/react/components/bulk-approval-card.js +36 -0
  51. package/dist/cjs/react/components/chat-input.d.ts +10 -0
  52. package/dist/cjs/react/components/chat-input.js +60 -0
  53. package/dist/cjs/react/components/chat-window.d.ts +2 -0
  54. package/dist/cjs/react/components/chat-window.js +134 -0
  55. package/dist/cjs/react/components/confirm-dialog.d.ts +32 -0
  56. package/dist/cjs/react/components/confirm-dialog.js +48 -0
  57. package/dist/cjs/react/components/floating-chat-ui.d.ts +90 -0
  58. package/dist/cjs/react/components/floating-chat-ui.js +163 -0
  59. package/dist/cjs/react/components/mention-chip.d.ts +16 -0
  60. package/dist/cjs/react/components/mention-chip.js +42 -0
  61. package/dist/cjs/react/components/mention-input.d.ts +8 -0
  62. package/dist/cjs/react/components/mention-input.js +117 -0
  63. package/dist/cjs/react/components/message-bubble.d.ts +11 -0
  64. package/dist/cjs/react/components/message-bubble.js +171 -0
  65. package/dist/cjs/react/components/message-list.d.ts +8 -0
  66. package/dist/cjs/react/components/message-list.js +52 -0
  67. package/dist/cjs/react/components/pipeline-timeline.d.ts +7 -0
  68. package/dist/cjs/react/components/pipeline-timeline.js +72 -0
  69. package/dist/cjs/react/components/recording-bar.d.ts +2 -0
  70. package/dist/cjs/react/components/recording-bar.js +81 -0
  71. package/dist/cjs/react/components/recording-controls.d.ts +18 -0
  72. package/dist/cjs/react/components/recording-controls.js +38 -0
  73. package/dist/cjs/react/components/rich-mention-input.d.ts +2 -0
  74. package/dist/cjs/react/components/rich-mention-input.js +571 -0
  75. package/dist/cjs/react/components/session-switcher.d.ts +14 -0
  76. package/dist/cjs/react/components/session-switcher.js +133 -0
  77. package/dist/cjs/react/components/suggestion-bar.d.ts +9 -0
  78. package/dist/cjs/react/components/suggestion-bar.js +47 -0
  79. package/dist/cjs/react/components/suggestion-chip.d.ts +9 -0
  80. package/dist/cjs/react/components/suggestion-chip.js +14 -0
  81. package/dist/cjs/react/components/voice-input-addon.d.ts +13 -0
  82. package/dist/cjs/react/components/voice-input-addon.js +31 -0
  83. package/dist/cjs/react/components/voice-input-button.d.ts +2 -0
  84. package/dist/cjs/react/components/voice-input-button.js +55 -0
  85. package/dist/cjs/react/components/wavesurfer-loader.d.ts +5 -0
  86. package/dist/cjs/react/components/wavesurfer-loader.js +36 -0
  87. package/dist/cjs/react/hooks/use-ai-chat.d.ts +22 -0
  88. package/dist/cjs/react/hooks/use-ai-chat.js +51 -0
  89. package/dist/cjs/react/hooks/use-approvals.d.ts +23 -0
  90. package/dist/cjs/react/hooks/use-approvals.js +63 -0
  91. package/dist/cjs/react/hooks/use-deepgram-transcription.d.ts +2 -0
  92. package/dist/cjs/react/hooks/use-deepgram-transcription.js +329 -0
  93. package/dist/cjs/react/hooks/use-drag-resize-panel.d.ts +48 -0
  94. package/dist/cjs/react/hooks/use-drag-resize-panel.js +308 -0
  95. package/dist/cjs/react/hooks/use-draggable-position.d.ts +25 -0
  96. package/dist/cjs/react/hooks/use-draggable-position.js +134 -0
  97. package/dist/cjs/react/hooks/use-keyboard-shortcut.d.ts +20 -0
  98. package/dist/cjs/react/hooks/use-keyboard-shortcut.js +93 -0
  99. package/dist/cjs/react/hooks/use-mentions.d.ts +23 -0
  100. package/dist/cjs/react/hooks/use-mentions.js +42 -0
  101. package/dist/cjs/react/hooks/use-suggestions.d.ts +22 -0
  102. package/dist/cjs/react/hooks/use-suggestions.js +39 -0
  103. package/dist/cjs/react/index.d.ts +43 -0
  104. package/dist/cjs/react/index.js +111 -0
  105. package/dist/cjs/react/provider/ai-chat-provider.d.ts +45 -0
  106. package/dist/cjs/react/provider/ai-chat-provider.js +1024 -0
  107. package/dist/cjs/react/provider/chat-context.d.ts +84 -0
  108. package/dist/cjs/react/provider/chat-context.js +13 -0
  109. package/dist/cjs/react/template.d.ts +84 -0
  110. package/dist/cjs/react/template.js +90 -0
  111. package/dist/cjs/react/themes.d.ts +24 -0
  112. package/dist/cjs/react/themes.js +273 -0
  113. package/dist/cjs/react/transports/langgraph/index.d.ts +2 -0
  114. package/dist/cjs/react/transports/langgraph/index.js +8 -0
  115. package/dist/cjs/react/transports/langgraph/langgraph-ai-chat.d.ts +62 -0
  116. package/dist/cjs/react/transports/langgraph/langgraph-ai-chat.js +94 -0
  117. package/dist/cjs/react/transports/langgraph.d.ts +71 -0
  118. package/dist/cjs/react/transports/langgraph.js +369 -0
  119. package/dist/cjs/react/transports/vercel/index.d.ts +2 -0
  120. package/dist/cjs/react/transports/vercel/index.js +8 -0
  121. package/dist/cjs/react/transports/vercel/vercel-ai-chat.d.ts +49 -0
  122. package/dist/cjs/react/transports/vercel/vercel-ai-chat.js +81 -0
  123. package/dist/cjs/react/transports/vercel.d.ts +30 -0
  124. package/dist/cjs/react/transports/vercel.js +69 -0
  125. package/dist/cjs/react/types.d.ts +760 -0
  126. package/dist/cjs/react/types.js +2 -0
  127. package/dist/cjs/react/ui.d.ts +73 -0
  128. package/dist/cjs/react/ui.js +183 -0
  129. package/dist/cjs/react/utils/create-approval-description.d.ts +53 -0
  130. package/dist/cjs/react/utils/create-approval-description.js +122 -0
  131. package/dist/cjs/react/utils/create-tool-result-handler.d.ts +90 -0
  132. package/dist/cjs/react/utils/create-tool-result-handler.js +168 -0
  133. package/dist/cjs/react/utils/mention-markup.d.ts +13 -0
  134. package/dist/cjs/react/utils/mention-markup.js +117 -0
  135. package/dist/cjs/react/utils/merge-pipeline-steps.d.ts +19 -0
  136. package/dist/cjs/react/utils/merge-pipeline-steps.js +81 -0
  137. package/dist/cjs/react/utils/sanitize-stored-messages.d.ts +23 -0
  138. package/dist/cjs/react/utils/sanitize-stored-messages.js +52 -0
  139. package/dist/cjs/server/adapters/nestjs/chat/ai-chat-auth.guard.d.ts +10 -0
  140. package/dist/cjs/server/adapters/nestjs/chat/ai-chat-auth.guard.js +42 -0
  141. package/dist/cjs/server/adapters/nestjs/chat/ai-chat-module-options.interface.d.ts +32 -0
  142. package/dist/cjs/server/adapters/nestjs/chat/ai-chat-module-options.interface.js +4 -0
  143. package/dist/cjs/server/adapters/nestjs/chat/ai-chat.controller.d.ts +11 -0
  144. package/dist/cjs/server/adapters/nestjs/chat/ai-chat.controller.js +80 -0
  145. package/dist/cjs/server/adapters/nestjs/chat/ai-chat.module.d.ts +9 -0
  146. package/dist/cjs/server/adapters/nestjs/chat/ai-chat.module.js +119 -0
  147. package/dist/cjs/server/adapters/nestjs/chat/ai-chat.service.d.ts +13 -0
  148. package/dist/cjs/server/adapters/nestjs/chat/ai-chat.service.js +131 -0
  149. package/dist/cjs/server/adapters/nestjs/chat/dto/ai-chat-request.dto.d.ts +7 -0
  150. package/dist/cjs/server/adapters/nestjs/chat/dto/ai-chat-request.dto.js +52 -0
  151. package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message-part.dto.d.ts +19 -0
  152. package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message-part.dto.js +108 -0
  153. package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message.dto.d.ts +8 -0
  154. package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message.dto.js +59 -0
  155. package/dist/cjs/server/adapters/nestjs/chat/dto/index.d.ts +3 -0
  156. package/dist/cjs/server/adapters/nestjs/chat/dto/index.js +9 -0
  157. package/dist/cjs/server/adapters/nestjs/chat/index.d.ts +3 -0
  158. package/dist/cjs/server/adapters/nestjs/chat/index.js +9 -0
  159. package/dist/cjs/server/adapters/nestjs/chat/speech-stream.gateway.d.ts +4 -0
  160. package/dist/cjs/server/adapters/nestjs/chat/speech-stream.gateway.js +73 -0
  161. package/dist/cjs/server/adapters/nestjs/chat/ws-loader.d.ts +5 -0
  162. package/dist/cjs/server/adapters/nestjs/chat/ws-loader.js +72 -0
  163. package/dist/cjs/server/adapters/nestjs/decorators/ai-module.decorator.d.ts +6 -0
  164. package/dist/cjs/server/adapters/nestjs/decorators/ai-module.decorator.js +13 -0
  165. package/dist/cjs/server/adapters/nestjs/decorators/ai-param.decorator.d.ts +5 -0
  166. package/dist/cjs/server/adapters/nestjs/decorators/ai-param.decorator.js +15 -0
  167. package/dist/cjs/server/adapters/nestjs/decorators/ai-tool.decorator.d.ts +6 -0
  168. package/dist/cjs/server/adapters/nestjs/decorators/ai-tool.decorator.js +22 -0
  169. package/dist/cjs/server/adapters/nestjs/decorators/constants.d.ts +4 -0
  170. package/dist/cjs/server/adapters/nestjs/decorators/constants.js +7 -0
  171. package/dist/cjs/server/adapters/nestjs/index.d.ts +14 -0
  172. package/dist/cjs/server/adapters/nestjs/index.js +42 -0
  173. package/dist/cjs/server/adapters/nestjs/metadata.d.ts +39 -0
  174. package/dist/cjs/server/adapters/nestjs/metadata.js +36 -0
  175. package/dist/cjs/server/adapters/nestjs/module/ai-tools-explorer.service.d.ts +36 -0
  176. package/dist/cjs/server/adapters/nestjs/module/ai-tools-explorer.service.js +159 -0
  177. package/dist/cjs/server/adapters/nestjs/module/ai-tools.module.d.ts +37 -0
  178. package/dist/cjs/server/adapters/nestjs/module/ai-tools.module.js +44 -0
  179. package/dist/cjs/server/adapters/nestjs/module/ai-tools.service.d.ts +28 -0
  180. package/dist/cjs/server/adapters/nestjs/module/ai-tools.service.js +41 -0
  181. package/dist/cjs/server/adapters/nestjs/scanner/nestjs-scanner.d.ts +16 -0
  182. package/dist/cjs/server/adapters/nestjs/scanner/nestjs-scanner.js +117 -0
  183. package/dist/cjs/server/adapters/nestjs/scanner/schema-builder.d.ts +26 -0
  184. package/dist/cjs/server/adapters/nestjs/scanner/schema-builder.js +259 -0
  185. package/dist/cjs/server/adapters/nestjs/swagger.d.ts +7 -0
  186. package/dist/cjs/server/adapters/nestjs/swagger.js +39 -0
  187. package/dist/cjs/server/adapters/nextjs/context-helpers.d.ts +22 -0
  188. package/dist/cjs/server/adapters/nextjs/context-helpers.js +60 -0
  189. package/dist/cjs/server/adapters/nextjs/create-ai-chat-handler.d.ts +94 -0
  190. package/dist/cjs/server/adapters/nextjs/create-ai-chat-handler.js +965 -0
  191. package/dist/cjs/server/adapters/nextjs/filter-fallback-error.d.ts +11 -0
  192. package/dist/cjs/server/adapters/nextjs/filter-fallback-error.js +41 -0
  193. package/dist/cjs/server/adapters/nextjs/index.d.ts +8 -0
  194. package/dist/cjs/server/adapters/nextjs/index.js +22 -0
  195. package/dist/cjs/server/adapters/nextjs/prepare-model-messages.d.ts +58 -0
  196. package/dist/cjs/server/adapters/nextjs/prepare-model-messages.js +114 -0
  197. package/dist/cjs/server/adapters/nextjs/sanitize-model-messages.d.ts +64 -0
  198. package/dist/cjs/server/adapters/nextjs/sanitize-model-messages.js +489 -0
  199. package/dist/cjs/server/adapters/nextjs/server-action-helper.d.ts +7 -0
  200. package/dist/cjs/server/adapters/nextjs/server-action-helper.js +40 -0
  201. package/dist/cjs/server/adapters/nextjs/types.d.ts +246 -0
  202. package/dist/cjs/server/adapters/nextjs/types.js +2 -0
  203. package/dist/cjs/server/adapters/nextjs/with-ai-tool.d.ts +9 -0
  204. package/dist/cjs/server/adapters/nextjs/with-ai-tool.js +12 -0
  205. package/dist/cjs/server/core/followup-tool.d.ts +9 -0
  206. package/dist/cjs/server/core/followup-tool.js +82 -0
  207. package/dist/cjs/server/core/index.d.ts +139 -0
  208. package/dist/cjs/server/core/index.js +818 -0
  209. package/dist/cjs/server/core/intent-classifier.d.ts +94 -0
  210. package/dist/cjs/server/core/intent-classifier.js +324 -0
  211. package/dist/cjs/server/core/model-selector.d.ts +12 -0
  212. package/dist/cjs/server/core/model-selector.js +48 -0
  213. package/dist/cjs/server/core/module-router.d.ts +14 -0
  214. package/dist/cjs/server/core/module-router.js +110 -0
  215. package/dist/cjs/server/core/zod-compact.d.ts +18 -0
  216. package/dist/cjs/server/core/zod-compact.js +129 -0
  217. package/dist/cjs/server/index.d.ts +6 -0
  218. package/dist/cjs/server/index.js +28 -0
  219. package/dist/cjs/server/lancer/cache.d.ts +9 -0
  220. package/dist/cjs/server/lancer/cache.js +33 -0
  221. package/dist/cjs/server/lancer/client.d.ts +2 -0
  222. package/dist/cjs/server/lancer/client.js +279 -0
  223. package/dist/cjs/server/lancer/errors.d.ts +13 -0
  224. package/dist/cjs/server/lancer/errors.js +21 -0
  225. package/dist/cjs/server/lancer/index.d.ts +3 -0
  226. package/dist/cjs/server/lancer/index.js +7 -0
  227. package/dist/cjs/server/lancer/telemetry-buffer.d.ts +15 -0
  228. package/dist/cjs/server/lancer/telemetry-buffer.js +64 -0
  229. package/dist/cjs/server/lancer/types.d.ts +201 -0
  230. package/dist/cjs/server/lancer/types.js +5 -0
  231. package/dist/cjs/server/pii-shield/aegis-detector.d.ts +26 -0
  232. package/dist/cjs/server/pii-shield/aegis-detector.js +26 -0
  233. package/dist/cjs/server/pii-shield/create-pii-shield.d.ts +2 -0
  234. package/dist/cjs/server/pii-shield/create-pii-shield.js +191 -0
  235. package/dist/cjs/server/pii-shield/deep-walk.d.ts +1 -0
  236. package/dist/cjs/server/pii-shield/deep-walk.js +19 -0
  237. package/dist/cjs/server/pii-shield/fake-data.d.ts +5 -0
  238. package/dist/cjs/server/pii-shield/fake-data.js +170 -0
  239. package/dist/cjs/server/pii-shield/index.d.ts +4 -0
  240. package/dist/cjs/server/pii-shield/index.js +9 -0
  241. package/dist/cjs/server/pii-shield/leakage.d.ts +1 -0
  242. package/dist/cjs/server/pii-shield/leakage.js +15 -0
  243. package/dist/cjs/server/pii-shield/session-mapping.d.ts +12 -0
  244. package/dist/cjs/server/pii-shield/session-mapping.js +70 -0
  245. package/dist/cjs/server/pipeline.d.ts +26 -0
  246. package/dist/cjs/server/pipeline.js +127 -0
  247. package/dist/cjs/server/testing/approvals.d.ts +11 -0
  248. package/dist/cjs/server/testing/approvals.js +41 -0
  249. package/dist/cjs/server/testing/combination.d.ts +3 -0
  250. package/dist/cjs/server/testing/combination.js +83 -0
  251. package/dist/cjs/server/testing/create-test-suite.d.ts +29 -0
  252. package/dist/cjs/server/testing/create-test-suite.js +81 -0
  253. package/dist/cjs/server/testing/edge-cases.d.ts +8 -0
  254. package/dist/cjs/server/testing/edge-cases.js +277 -0
  255. package/dist/cjs/server/testing/guardrails.d.ts +14 -0
  256. package/dist/cjs/server/testing/guardrails.js +152 -0
  257. package/dist/cjs/server/testing/helpers.d.ts +14 -0
  258. package/dist/cjs/server/testing/helpers.js +119 -0
  259. package/dist/cjs/server/testing/index.d.ts +12 -0
  260. package/dist/cjs/server/testing/index.js +37 -0
  261. package/dist/cjs/server/testing/mock-executor.d.ts +8 -0
  262. package/dist/cjs/server/testing/mock-executor.js +40 -0
  263. package/dist/cjs/server/testing/regression.d.ts +7 -0
  264. package/dist/cjs/server/testing/regression.js +27 -0
  265. package/dist/cjs/server/testing/routing.d.ts +12 -0
  266. package/dist/cjs/server/testing/routing.js +63 -0
  267. package/dist/cjs/server/testing/schema-consistency.d.ts +6 -0
  268. package/dist/cjs/server/testing/schema-consistency.js +56 -0
  269. package/dist/cjs/server/testing/smoke.d.ts +11 -0
  270. package/dist/cjs/server/testing/smoke.js +64 -0
  271. package/dist/cjs/server/testing/types.d.ts +156 -0
  272. package/dist/cjs/server/testing/types.js +2 -0
  273. package/dist/cjs/types.d.ts +696 -0
  274. package/dist/cjs/types.js +91 -0
  275. package/dist/cli/commands/install-skills.d.ts +17 -0
  276. package/dist/cli/commands/install-skills.js +185 -0
  277. package/dist/cli/commands/install-skills.js.map +1 -0
  278. package/dist/cli/index.js +25 -0
  279. package/dist/cli/index.js.map +1 -1
  280. package/dist/react/components/ai-trigger-button.js +4 -6
  281. package/dist/react/components/ai-trigger-button.js.map +1 -1
  282. package/dist/react/components/recording-bar.js +4 -6
  283. package/dist/react/components/recording-bar.js.map +1 -1
  284. package/dist/react/components/wavesurfer-loader.d.ts +5 -0
  285. package/dist/react/components/wavesurfer-loader.js +34 -0
  286. package/dist/react/components/wavesurfer-loader.js.map +1 -0
  287. package/dist/server/adapters/nestjs/chat/ai-chat.module.d.ts +1 -1
  288. package/dist/server/adapters/nestjs/chat/ai-chat.module.js +3 -2
  289. package/dist/server/adapters/nestjs/chat/ai-chat.module.js.map +1 -1
  290. package/dist/server/adapters/nestjs/chat/speech-stream.gateway.d.ts +1 -1
  291. package/dist/server/adapters/nestjs/chat/speech-stream.gateway.js +10 -8
  292. package/dist/server/adapters/nestjs/chat/speech-stream.gateway.js.map +1 -1
  293. package/dist/server/adapters/nestjs/chat/ws-loader.d.ts +5 -0
  294. package/dist/server/adapters/nestjs/chat/ws-loader.js +37 -0
  295. package/dist/server/adapters/nestjs/chat/ws-loader.js.map +1 -0
  296. package/openapi/package.json +5 -0
  297. package/package.json +59 -16
  298. package/react/langgraph/package.json +5 -0
  299. package/react/package.json +5 -0
  300. package/react/vercel/package.json +5 -0
  301. package/server/nestjs/package.json +5 -0
  302. package/server/nextjs/package.json +5 -0
  303. package/server/package.json +5 -0
  304. package/server/testing/package.json +5 -0
  305. package/skills/README.md +107 -0
  306. package/skills/building-glirastes-chat-ui/SKILL.md +378 -0
  307. package/skills/integrating-glirastes-nestjs/SKILL.md +346 -0
  308. package/skills/integrating-glirastes-nextjs/SKILL.md +312 -0
  309. package/skills/integrating-glirastes-nextjs-with-nestjs-backend/SKILL.md +359 -0
  310. package/skills/maintaining-glirastes-tools/SKILL.md +467 -0
@@ -0,0 +1,329 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useDeepgramTranscription = useDeepgramTranscription;
4
+ const react_1 = require("react");
5
+ const DEFAULT_LANGUAGE = 'de';
6
+ const CONNECT_TIMEOUT_MS = 5000;
7
+ const SPEECH_STREAM_ENDPOINT = '/api/ai/speech-stream';
8
+ function useDeepgramTranscription(options = {}) {
9
+ const { language = DEFAULT_LANGUAGE, baseUrl, getToken, onInterimTranscript, onError, } = options;
10
+ const [isRecording, setIsRecording] = (0, react_1.useState)(false);
11
+ const [isConnecting, setIsConnecting] = (0, react_1.useState)(false);
12
+ const [error, setError] = (0, react_1.useState)(null);
13
+ const [transcript, setTranscript] = (0, react_1.useState)('');
14
+ const socketRef = (0, react_1.useRef)(null);
15
+ const streamRef = (0, react_1.useRef)(null);
16
+ const audioContextRef = (0, react_1.useRef)(null);
17
+ const sourceRef = (0, react_1.useRef)(null);
18
+ const processorRef = (0, react_1.useRef)(null);
19
+ const connectTimeoutRef = (0, react_1.useRef)(null);
20
+ const mediaRecorderRef = (0, react_1.useRef)(null);
21
+ const abortControllerRef = (0, react_1.useRef)(null);
22
+ const transcriptRef = (0, react_1.useRef)('');
23
+ const lastFullTextRef = (0, react_1.useRef)('');
24
+ const clearConnectTimeout = (0, react_1.useCallback)(() => {
25
+ if (connectTimeoutRef.current) {
26
+ clearTimeout(connectTimeoutRef.current);
27
+ connectTimeoutRef.current = null;
28
+ }
29
+ }, []);
30
+ const cleanup = (0, react_1.useCallback)(() => {
31
+ clearConnectTimeout();
32
+ if (abortControllerRef.current) {
33
+ abortControllerRef.current.abort();
34
+ abortControllerRef.current = null;
35
+ }
36
+ if (mediaRecorderRef.current) {
37
+ const recorder = mediaRecorderRef.current;
38
+ if (recorder.state !== 'inactive') {
39
+ recorder.stop();
40
+ }
41
+ mediaRecorderRef.current = null;
42
+ }
43
+ if (processorRef.current) {
44
+ processorRef.current.disconnect();
45
+ processorRef.current = null;
46
+ }
47
+ if (sourceRef.current) {
48
+ sourceRef.current.disconnect();
49
+ sourceRef.current = null;
50
+ }
51
+ if (audioContextRef.current) {
52
+ void audioContextRef.current.close();
53
+ audioContextRef.current = null;
54
+ }
55
+ if (socketRef.current) {
56
+ const socket = socketRef.current;
57
+ socket.onopen = null;
58
+ socket.onmessage = null;
59
+ socket.onerror = null;
60
+ socket.onclose = null;
61
+ if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
62
+ socket.close();
63
+ }
64
+ socketRef.current = null;
65
+ }
66
+ if (streamRef.current) {
67
+ for (const track of streamRef.current.getTracks()) {
68
+ track.stop();
69
+ }
70
+ streamRef.current = null;
71
+ }
72
+ }, [clearConnectTimeout]);
73
+ (0, react_1.useEffect)(() => cleanup, [cleanup]);
74
+ const determineTransportAndUrl = (0, react_1.useCallback)((token) => {
75
+ const tokenParam = token ? `&token=${encodeURIComponent(token)}` : '';
76
+ if (baseUrl) {
77
+ const urlLower = baseUrl.toLowerCase();
78
+ if (urlLower.startsWith('ws://') || urlLower.startsWith('wss://')) {
79
+ return { transport: 'websocket', url: `${baseUrl}?language=${encodeURIComponent(language)}${tokenParam}` };
80
+ }
81
+ else if (urlLower.startsWith('http://') || urlLower.startsWith('https://')) {
82
+ return { transport: 'http', url: `${baseUrl}?language=${encodeURIComponent(language)}${tokenParam}` };
83
+ }
84
+ }
85
+ // Default: WebSocket from current origin
86
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
87
+ const wsUrl = `${protocol}//${window.location.host}${SPEECH_STREAM_ENDPOINT}?language=${encodeURIComponent(language)}${tokenParam}`;
88
+ return { transport: 'websocket', url: wsUrl };
89
+ }, [baseUrl, language]);
90
+ const handleTranscriptMessage = (0, react_1.useCallback)((data) => {
91
+ if (data.type === 'error') {
92
+ const errMsg = data.message ?? 'Transcription error';
93
+ setError(errMsg);
94
+ onError?.(errMsg);
95
+ return;
96
+ }
97
+ const text = data.channel?.alternatives?.[0]?.transcript;
98
+ if (!text)
99
+ return;
100
+ if (data.is_final) {
101
+ if (text.trim()) {
102
+ transcriptRef.current += (transcriptRef.current ? ' ' : '') + text;
103
+ setTranscript(transcriptRef.current);
104
+ lastFullTextRef.current = transcriptRef.current;
105
+ }
106
+ return;
107
+ }
108
+ const fullText = `${transcriptRef.current}${transcriptRef.current ? ' ' : ''}${text}`;
109
+ lastFullTextRef.current = fullText;
110
+ onInterimTranscript?.(fullText);
111
+ }, [onError, onInterimTranscript]);
112
+ const startHttpStreaming = (0, react_1.useCallback)(async (stream, url) => {
113
+ const abortController = new AbortController();
114
+ abortControllerRef.current = abortController;
115
+ const mediaRecorder = new MediaRecorder(stream, {
116
+ mimeType: 'audio/webm;codecs=opus',
117
+ });
118
+ mediaRecorderRef.current = mediaRecorder;
119
+ mediaRecorder.start(250); // Send chunks every 250ms
120
+ try {
121
+ const response = await fetch(url, {
122
+ method: 'POST',
123
+ headers: {
124
+ 'Content-Type': 'application/octet-stream',
125
+ },
126
+ body: new ReadableStream({
127
+ start(controller) {
128
+ mediaRecorder.ondataavailable = (event) => {
129
+ if (event.data.size > 0) {
130
+ void event.data.arrayBuffer().then((buffer) => {
131
+ controller.enqueue(new Uint8Array(buffer));
132
+ });
133
+ }
134
+ };
135
+ mediaRecorder.onstop = () => {
136
+ controller.close();
137
+ };
138
+ },
139
+ }),
140
+ // @ts-expect-error - duplex is required for streaming request body but not in TS types yet
141
+ duplex: 'half',
142
+ signal: abortController.signal,
143
+ });
144
+ if (!response.ok) {
145
+ throw new Error('HTTP streaming failed');
146
+ }
147
+ if (!response.body) {
148
+ throw new Error('No response body');
149
+ }
150
+ setIsConnecting(false);
151
+ setIsRecording(true);
152
+ const reader = response.body.getReader();
153
+ const decoder = new TextDecoder();
154
+ let buffer = '';
155
+ try {
156
+ while (true) {
157
+ const { done, value } = await reader.read();
158
+ if (done)
159
+ break;
160
+ buffer += decoder.decode(value, { stream: true });
161
+ const lines = buffer.split('\n');
162
+ buffer = lines.pop() ?? '';
163
+ for (const line of lines) {
164
+ if (line.startsWith('data: ')) {
165
+ const jsonStr = line.slice(6);
166
+ try {
167
+ const data = JSON.parse(jsonStr);
168
+ handleTranscriptMessage(data);
169
+ }
170
+ catch {
171
+ // Ignore malformed JSON
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ catch (err) {
178
+ if (abortController.signal.aborted) {
179
+ // Normal abort, not an error
180
+ return;
181
+ }
182
+ throw err;
183
+ }
184
+ }
185
+ catch (err) {
186
+ if (abortController.signal.aborted) {
187
+ // Normal abort, not an error
188
+ return;
189
+ }
190
+ const errMsg = err instanceof Error ? err.message : 'Verbindungsfehler';
191
+ setError(errMsg);
192
+ onError?.(errMsg);
193
+ cleanup();
194
+ setIsRecording(false);
195
+ setIsConnecting(false);
196
+ }
197
+ }, [cleanup, onError, handleTranscriptMessage]);
198
+ const startWebSocketStreaming = (0, react_1.useCallback)(async (stream, wsUrl) => {
199
+ const socket = new WebSocket(wsUrl);
200
+ socketRef.current = socket;
201
+ await new Promise((resolve, reject) => {
202
+ connectTimeoutRef.current = setTimeout(() => {
203
+ reject(new Error('Verbindungs-Timeout'));
204
+ }, CONNECT_TIMEOUT_MS);
205
+ socket.onopen = () => {
206
+ clearConnectTimeout();
207
+ resolve();
208
+ };
209
+ socket.onerror = () => {
210
+ clearConnectTimeout();
211
+ reject(new Error('Verbindungsfehler'));
212
+ };
213
+ });
214
+ socket.onmessage = (event) => {
215
+ try {
216
+ const payload = typeof event.data === 'string' ? event.data : '';
217
+ if (!payload)
218
+ return;
219
+ const data = JSON.parse(payload);
220
+ handleTranscriptMessage(data);
221
+ }
222
+ catch {
223
+ // Ignore malformed messages
224
+ }
225
+ };
226
+ socket.onerror = () => {
227
+ const errMsg = 'Verbindungsfehler';
228
+ setError(errMsg);
229
+ onError?.(errMsg);
230
+ cleanup();
231
+ setIsRecording(false);
232
+ setIsConnecting(false);
233
+ };
234
+ socket.onclose = () => {
235
+ setIsRecording(false);
236
+ setIsConnecting(false);
237
+ };
238
+ const AudioContextCtor = globalThis.AudioContext ??
239
+ (globalThis.webkitAudioContext);
240
+ if (!AudioContextCtor) {
241
+ throw new Error('Browser unterstuetzt keine Audioaufnahme');
242
+ }
243
+ const audioContext = new AudioContextCtor({ sampleRate: 16000 });
244
+ try {
245
+ await audioContext.audioWorklet.addModule('/audio-worklet-processor.js');
246
+ }
247
+ catch {
248
+ throw new Error('AudioWorklet nicht verfuegbar');
249
+ }
250
+ const source = audioContext.createMediaStreamSource(stream);
251
+ const processor = new AudioWorkletNode(audioContext, 'audio-processor');
252
+ processor.port.onmessage = (event) => {
253
+ if (socket.readyState !== WebSocket.OPEN)
254
+ return;
255
+ socket.send(event.data.buffer);
256
+ };
257
+ source.connect(processor);
258
+ processor.connect(audioContext.destination);
259
+ audioContextRef.current = audioContext;
260
+ sourceRef.current = source;
261
+ processorRef.current = processor;
262
+ setIsConnecting(false);
263
+ setIsRecording(true);
264
+ }, [clearConnectTimeout, cleanup, onError, handleTranscriptMessage]);
265
+ const startRecording = (0, react_1.useCallback)(async () => {
266
+ setError(null);
267
+ setTranscript('');
268
+ transcriptRef.current = '';
269
+ lastFullTextRef.current = '';
270
+ setIsConnecting(true);
271
+ try {
272
+ if (!navigator?.mediaDevices?.getUserMedia) {
273
+ throw new Error('Browser unterstuetzt keine Audioaufnahme');
274
+ }
275
+ let stream;
276
+ try {
277
+ stream = await navigator.mediaDevices.getUserMedia({
278
+ audio: {
279
+ channelCount: 1,
280
+ sampleRate: 16000,
281
+ },
282
+ });
283
+ }
284
+ catch {
285
+ throw new Error('Mikrofon-Zugriff verweigert');
286
+ }
287
+ streamRef.current = stream;
288
+ const token = getToken?.() ?? undefined;
289
+ const { transport, url } = determineTransportAndUrl(token);
290
+ // For WebSocket, token is required for authentication
291
+ // For HTTP, cookie-based auth can be used instead
292
+ if (transport === 'websocket' && !token) {
293
+ throw new Error('Authentication token required for WebSocket speech stream');
294
+ }
295
+ if (transport === 'http') {
296
+ await startHttpStreaming(stream, url);
297
+ }
298
+ else {
299
+ await startWebSocketStreaming(stream, url);
300
+ }
301
+ }
302
+ catch (err) {
303
+ const errMsg = err instanceof Error ? err.message : 'Unbekannter Fehler';
304
+ setError(errMsg);
305
+ onError?.(errMsg);
306
+ setIsConnecting(false);
307
+ setIsRecording(false);
308
+ cleanup();
309
+ }
310
+ }, [cleanup, onError, getToken, determineTransportAndUrl, startHttpStreaming, startWebSocketStreaming]);
311
+ const stopRecording = (0, react_1.useCallback)(() => {
312
+ const finalText = lastFullTextRef.current || transcriptRef.current;
313
+ if (finalText && finalText !== transcriptRef.current) {
314
+ setTranscript(finalText);
315
+ }
316
+ cleanup();
317
+ setIsRecording(false);
318
+ setIsConnecting(false);
319
+ return finalText;
320
+ }, [cleanup]);
321
+ return {
322
+ isRecording,
323
+ isConnecting,
324
+ error,
325
+ transcript,
326
+ startRecording,
327
+ stopRecording,
328
+ };
329
+ }
@@ -0,0 +1,48 @@
1
+ import { type RefObject, type MouseEvent as ReactMouseEvent } from 'react';
2
+ export interface UseDragResizePanelOptions {
3
+ panelRef: RefObject<HTMLElement | null>;
4
+ width: number;
5
+ height: number;
6
+ minWidth: number;
7
+ minHeight: number;
8
+ maxWidth?: number;
9
+ maxHeight?: number;
10
+ initialPosition?: {
11
+ x: number;
12
+ y: number;
13
+ };
14
+ snapToCenter?: boolean;
15
+ snapRadius?: number;
16
+ edgeThreshold?: number;
17
+ persistDimensions?: boolean | string;
18
+ draggable?: boolean;
19
+ resizable?: boolean;
20
+ }
21
+ export type ResizeEdge = 'top' | 'left' | 'right';
22
+ export interface UseDragResizePanelReturn {
23
+ position: {
24
+ x: number;
25
+ y: number;
26
+ } | null;
27
+ size: {
28
+ width: number;
29
+ height: number;
30
+ };
31
+ isDragging: boolean;
32
+ isResizing: boolean;
33
+ activeEdge: ResizeEdge | null;
34
+ isNearSnapZone: boolean;
35
+ nearEdge: {
36
+ top: boolean;
37
+ left: boolean;
38
+ right: boolean;
39
+ };
40
+ handlers: {
41
+ onDragStart: (e: ReactMouseEvent<HTMLElement>) => void;
42
+ onDoubleClickHeader: () => void;
43
+ onEdgeResizeStart: (edge: ResizeEdge) => (e: ReactMouseEvent<HTMLElement>) => void;
44
+ onPanelMouseMove: (e: ReactMouseEvent<HTMLElement>) => void;
45
+ onPanelMouseLeave: () => void;
46
+ };
47
+ }
48
+ export declare function useDragResizePanel(options: UseDragResizePanelOptions): UseDragResizePanelReturn;
@@ -0,0 +1,308 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useDragResizePanel = useDragResizePanel;
4
+ const react_1 = require("react");
5
+ // ---------------------------------------------------------------------------
6
+ // Constants
7
+ // ---------------------------------------------------------------------------
8
+ const DEFAULT_SNAP_RADIUS = 100;
9
+ const DEFAULT_EDGE_THRESHOLD = 20;
10
+ // ---------------------------------------------------------------------------
11
+ // Helpers
12
+ // ---------------------------------------------------------------------------
13
+ function readSessionNumber(key) {
14
+ if (typeof window === 'undefined')
15
+ return null;
16
+ try {
17
+ const stored = sessionStorage.getItem(key);
18
+ return stored ? Number(stored) : null;
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ function writeSessionNumber(key, value) {
25
+ if (typeof window === 'undefined')
26
+ return;
27
+ try {
28
+ sessionStorage.setItem(key, String(value));
29
+ }
30
+ catch {
31
+ // Ignore errors from private browsing or quota limits
32
+ }
33
+ }
34
+ // ---------------------------------------------------------------------------
35
+ // Hook
36
+ // ---------------------------------------------------------------------------
37
+ function useDragResizePanel(options) {
38
+ const { panelRef, width: defaultWidth, height: defaultHeight, minWidth, minHeight, maxWidth, maxHeight, initialPosition, snapToCenter = false, snapRadius = DEFAULT_SNAP_RADIUS, edgeThreshold = DEFAULT_EDGE_THRESHOLD, persistDimensions = false, draggable = true, resizable = true, } = options;
39
+ const storagePrefix = persistDimensions === true
40
+ ? 'chat-panel'
41
+ : typeof persistDimensions === 'string'
42
+ ? persistDimensions
43
+ : null;
44
+ // -----------------------------------------------------------------------
45
+ // Position state (null = default centered)
46
+ // -----------------------------------------------------------------------
47
+ const [position, setPosition] = (0, react_1.useState)(() => initialPosition ?? null);
48
+ // -----------------------------------------------------------------------
49
+ // Size state (with optional session-storage persistence)
50
+ // -----------------------------------------------------------------------
51
+ const [size, setSize] = (0, react_1.useState)(() => {
52
+ if (storagePrefix) {
53
+ const storedWidth = readSessionNumber(`${storagePrefix}-width`);
54
+ const storedHeight = readSessionNumber(`${storagePrefix}-height`);
55
+ return {
56
+ width: storedWidth ?? defaultWidth,
57
+ height: storedHeight ?? defaultHeight,
58
+ };
59
+ }
60
+ return { width: defaultWidth, height: defaultHeight };
61
+ });
62
+ // Sync to incoming prop changes. Without this, the `size` state is
63
+ // frozen at mount time and consumers updating their `size` / `width` /
64
+ // `height` props see no effect until a hard reload.
65
+ (0, react_1.useEffect)(() => {
66
+ setSize({ width: defaultWidth, height: defaultHeight });
67
+ }, [defaultWidth, defaultHeight]);
68
+ // -----------------------------------------------------------------------
69
+ // Interaction states
70
+ // -----------------------------------------------------------------------
71
+ const [isDragging, setIsDragging] = (0, react_1.useState)(false);
72
+ const [isResizing, setIsResizing] = (0, react_1.useState)(false);
73
+ const [activeEdge, setActiveEdge] = (0, react_1.useState)(null);
74
+ const [isNearSnapZone, setIsNearSnapZone] = (0, react_1.useState)(false);
75
+ const [nearEdge, setNearEdge] = (0, react_1.useState)({ top: false, left: false, right: false });
76
+ // -----------------------------------------------------------------------
77
+ // Refs for drag/resize start state (mutable, never trigger re-renders)
78
+ // -----------------------------------------------------------------------
79
+ const dragStartRef = (0, react_1.useRef)(null);
80
+ const resizeStartRef = (0, react_1.useRef)(null);
81
+ const wasDraggingRef = (0, react_1.useRef)(false);
82
+ // -----------------------------------------------------------------------
83
+ // Snap-to-center helpers
84
+ // -----------------------------------------------------------------------
85
+ const shouldSnapToCenter = (0, react_1.useCallback)((x, _y) => {
86
+ if (!snapToCenter || typeof window === 'undefined')
87
+ return false;
88
+ const panel = panelRef.current;
89
+ if (!panel)
90
+ return false;
91
+ const viewportCenterX = window.innerWidth / 2;
92
+ const panelHeight = panel.getBoundingClientRect().height;
93
+ const defaultY = window.innerHeight - 16 - panelHeight;
94
+ return (Math.abs(x - viewportCenterX) < snapRadius &&
95
+ Math.abs(_y - defaultY) < snapRadius);
96
+ }, [snapToCenter, panelRef, snapRadius]);
97
+ // -----------------------------------------------------------------------
98
+ // Drag handlers
99
+ // -----------------------------------------------------------------------
100
+ const handleDragStart = (0, react_1.useCallback)((e) => {
101
+ if (!draggable)
102
+ return;
103
+ if (e.button !== 0)
104
+ return;
105
+ if (e.target.closest('button'))
106
+ return;
107
+ const panel = panelRef.current;
108
+ if (!panel)
109
+ return;
110
+ const rect = panel.getBoundingClientRect();
111
+ // When position is null (centered), the rendering uses
112
+ // transform: translate(-50%, 0), so x represents the center of the panel.
113
+ // We compute the initial x as rect.left + rect.width / 2 for smooth drag.
114
+ dragStartRef.current = {
115
+ mouseX: e.clientX,
116
+ mouseY: e.clientY,
117
+ panelX: position?.x ?? rect.left + rect.width / 2,
118
+ panelY: position?.y ?? rect.top,
119
+ };
120
+ setIsDragging(true);
121
+ e.preventDefault();
122
+ }, [draggable, panelRef, position]);
123
+ const handleDoubleClickHeader = (0, react_1.useCallback)(() => {
124
+ if (snapToCenter) {
125
+ setPosition(null);
126
+ }
127
+ }, [snapToCenter]);
128
+ // -----------------------------------------------------------------------
129
+ // Edge resize handler (memoized, returns a function per edge)
130
+ // -----------------------------------------------------------------------
131
+ const handleEdgeResizeStart = (0, react_1.useCallback)((edge) => (e) => {
132
+ if (!resizable)
133
+ return;
134
+ if (e.button !== 0)
135
+ return;
136
+ const panel = panelRef.current;
137
+ if (!panel)
138
+ return;
139
+ const rect = panel.getBoundingClientRect();
140
+ resizeStartRef.current = {
141
+ mouseX: e.clientX,
142
+ mouseY: e.clientY,
143
+ width: rect.width,
144
+ height: rect.height,
145
+ positionX: position?.x ?? rect.left + rect.width / 2,
146
+ positionY: position?.y ?? rect.top,
147
+ edge,
148
+ };
149
+ setIsResizing(true);
150
+ setActiveEdge(edge);
151
+ e.preventDefault();
152
+ e.stopPropagation();
153
+ }, [resizable, panelRef, position]);
154
+ // -----------------------------------------------------------------------
155
+ // Edge detection
156
+ // -----------------------------------------------------------------------
157
+ const handlePanelMouseMove = (0, react_1.useCallback)((e) => {
158
+ const panel = panelRef.current;
159
+ if (!panel || isDragging || isResizing)
160
+ return;
161
+ const rect = panel.getBoundingClientRect();
162
+ setNearEdge({
163
+ top: e.clientY - rect.top < edgeThreshold,
164
+ left: e.clientX - rect.left < edgeThreshold,
165
+ right: e.clientX - rect.left > rect.width - edgeThreshold,
166
+ });
167
+ }, [panelRef, isDragging, isResizing, edgeThreshold]);
168
+ const handlePanelMouseLeave = (0, react_1.useCallback)(() => {
169
+ if (!isDragging && !isResizing) {
170
+ setNearEdge({ top: false, left: false, right: false });
171
+ }
172
+ }, [isDragging, isResizing]);
173
+ // -----------------------------------------------------------------------
174
+ // Persist dimensions to sessionStorage
175
+ // -----------------------------------------------------------------------
176
+ (0, react_1.useEffect)(() => {
177
+ if (storagePrefix) {
178
+ writeSessionNumber(`${storagePrefix}-width`, size.width);
179
+ writeSessionNumber(`${storagePrefix}-height`, size.height);
180
+ }
181
+ }, [storagePrefix, size.width, size.height]);
182
+ // -----------------------------------------------------------------------
183
+ // Global mouse listeners for active drag / resize
184
+ // -----------------------------------------------------------------------
185
+ (0, react_1.useEffect)(() => {
186
+ if (!isDragging && !isResizing)
187
+ return;
188
+ // Prevent accidental text selection during drag/resize
189
+ const prevUserSelect = document.body.style.userSelect;
190
+ document.body.style.userSelect = 'none';
191
+ const onMouseMove = (e) => {
192
+ if (isDragging && dragStartRef.current) {
193
+ const newX = dragStartRef.current.panelX +
194
+ (e.clientX - dragStartRef.current.mouseX);
195
+ const newY = dragStartRef.current.panelY +
196
+ (e.clientY - dragStartRef.current.mouseY);
197
+ setPosition({ x: newX, y: newY });
198
+ if (snapToCenter) {
199
+ setIsNearSnapZone(shouldSnapToCenter(newX, newY));
200
+ }
201
+ }
202
+ if (isResizing && resizeStartRef.current) {
203
+ const start = resizeStartRef.current;
204
+ if (start.edge === 'top') {
205
+ const deltaY = start.mouseY - e.clientY;
206
+ let newHeight = start.height + deltaY;
207
+ newHeight = Math.max(minHeight, newHeight);
208
+ if (maxHeight != null)
209
+ newHeight = Math.min(maxHeight, newHeight);
210
+ const actualDelta = newHeight - start.height;
211
+ setSize((prev) => ({ ...prev, height: newHeight }));
212
+ // Move position up so the bottom edge stays anchored
213
+ if (start.positionY != null) {
214
+ setPosition((prev) => prev ? { ...prev, y: start.positionY - actualDelta } : prev);
215
+ }
216
+ }
217
+ else if (start.edge === 'left') {
218
+ const deltaX = start.mouseX - e.clientX;
219
+ let newWidth = start.width + deltaX;
220
+ newWidth = Math.max(minWidth, newWidth);
221
+ if (maxWidth != null)
222
+ newWidth = Math.min(maxWidth, newWidth);
223
+ const actualDelta = newWidth - start.width;
224
+ setSize((prev) => ({ ...prev, width: newWidth }));
225
+ // Shift center left so the right edge stays anchored
226
+ // (position.x is center due to translate(-50%, 0))
227
+ if (start.positionX != null) {
228
+ setPosition((prev) => prev ? { ...prev, x: start.positionX - actualDelta / 2 } : prev);
229
+ }
230
+ }
231
+ else {
232
+ const deltaX = e.clientX - start.mouseX;
233
+ let newWidth = start.width + deltaX;
234
+ newWidth = Math.max(minWidth, newWidth);
235
+ if (maxWidth != null)
236
+ newWidth = Math.min(maxWidth, newWidth);
237
+ const actualDelta = newWidth - start.width;
238
+ setSize((prev) => ({ ...prev, width: newWidth }));
239
+ // Shift center right so the left edge stays anchored
240
+ if (start.positionX != null) {
241
+ setPosition((prev) => prev ? { ...prev, x: start.positionX + actualDelta / 2 } : prev);
242
+ }
243
+ }
244
+ }
245
+ };
246
+ const onMouseUp = () => {
247
+ if (isDragging) {
248
+ wasDraggingRef.current = true;
249
+ setIsDragging(false);
250
+ setIsNearSnapZone(false);
251
+ dragStartRef.current = null;
252
+ }
253
+ if (isResizing) {
254
+ setIsResizing(false);
255
+ setActiveEdge(null);
256
+ resizeStartRef.current = null;
257
+ }
258
+ };
259
+ document.addEventListener('mousemove', onMouseMove);
260
+ document.addEventListener('mouseup', onMouseUp);
261
+ return () => {
262
+ document.body.style.userSelect = prevUserSelect;
263
+ document.removeEventListener('mousemove', onMouseMove);
264
+ document.removeEventListener('mouseup', onMouseUp);
265
+ };
266
+ }, [
267
+ isDragging,
268
+ isResizing,
269
+ snapToCenter,
270
+ shouldSnapToCenter,
271
+ minWidth,
272
+ minHeight,
273
+ maxWidth,
274
+ maxHeight,
275
+ ]);
276
+ // -----------------------------------------------------------------------
277
+ // Snap-on-release: detect transition from isDragging=true to false
278
+ // -----------------------------------------------------------------------
279
+ (0, react_1.useEffect)(() => {
280
+ if (!isDragging && wasDraggingRef.current) {
281
+ wasDraggingRef.current = false;
282
+ if (snapToCenter &&
283
+ position &&
284
+ shouldSnapToCenter(position.x, position.y)) {
285
+ setPosition(null);
286
+ }
287
+ }
288
+ }, [isDragging, snapToCenter, position, shouldSnapToCenter]);
289
+ // -----------------------------------------------------------------------
290
+ // Return
291
+ // -----------------------------------------------------------------------
292
+ return {
293
+ position,
294
+ size,
295
+ isDragging,
296
+ isResizing,
297
+ activeEdge,
298
+ isNearSnapZone,
299
+ nearEdge,
300
+ handlers: {
301
+ onDragStart: handleDragStart,
302
+ onDoubleClickHeader: handleDoubleClickHeader,
303
+ onEdgeResizeStart: handleEdgeResizeStart,
304
+ onPanelMouseMove: handlePanelMouseMove,
305
+ onPanelMouseLeave: handlePanelMouseLeave,
306
+ },
307
+ };
308
+ }