collavre 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 (410) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +221 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/stylesheets/collavre/actiontext.css +577 -0
  5. data/app/assets/stylesheets/collavre/activity_logs.css +99 -0
  6. data/app/assets/stylesheets/collavre/comments_popup.css +692 -0
  7. data/app/assets/stylesheets/collavre/creatives.css +559 -0
  8. data/app/assets/stylesheets/collavre/dark_mode.css +118 -0
  9. data/app/assets/stylesheets/collavre/mention_menu.css +43 -0
  10. data/app/assets/stylesheets/collavre/popup.css +160 -0
  11. data/app/assets/stylesheets/collavre/print.css +37 -0
  12. data/app/assets/stylesheets/collavre/slide_view.css +79 -0
  13. data/app/assets/stylesheets/collavre/user_menu.css +34 -0
  14. data/app/channels/collavre/comments_presence_channel.rb +54 -0
  15. data/app/channels/collavre/slide_view_channel.rb +11 -0
  16. data/app/channels/collavre/topics_channel.rb +12 -0
  17. data/app/components/collavre/avatar_component.html.erb +15 -0
  18. data/app/components/collavre/avatar_component.rb +59 -0
  19. data/app/components/collavre/inbox/badge_component.html.erb +6 -0
  20. data/app/components/collavre/inbox/badge_component.rb +18 -0
  21. data/app/components/collavre/plans_timeline_component.html.erb +14 -0
  22. data/app/components/collavre/plans_timeline_component.rb +56 -0
  23. data/app/components/collavre/popup_menu_component.html.erb +6 -0
  24. data/app/components/collavre/popup_menu_component.rb +30 -0
  25. data/app/components/collavre/progress_filter_component.html.erb +5 -0
  26. data/app/components/collavre/progress_filter_component.rb +10 -0
  27. data/app/components/collavre/user_mention_menu_component.html.erb +3 -0
  28. data/app/components/collavre/user_mention_menu_component.rb +8 -0
  29. data/app/controllers/collavre/application_controller.rb +15 -0
  30. data/app/controllers/collavre/attachments_controller.rb +44 -0
  31. data/app/controllers/collavre/calendar_events_controller.rb +15 -0
  32. data/app/controllers/collavre/comment_read_pointers_controller.rb +80 -0
  33. data/app/controllers/collavre/comments/activity_logs_controller.rb +26 -0
  34. data/app/controllers/collavre/comments/reactions_controller.rb +82 -0
  35. data/app/controllers/collavre/comments_controller.rb +464 -0
  36. data/app/controllers/collavre/contacts_controller.rb +10 -0
  37. data/app/controllers/collavre/creative_expanded_states_controller.rb +27 -0
  38. data/app/controllers/collavre/creative_imports_controller.rb +24 -0
  39. data/app/controllers/collavre/creative_plans_controller.rb +69 -0
  40. data/app/controllers/collavre/creative_shares_controller.rb +79 -0
  41. data/app/controllers/collavre/creatives_controller.rb +535 -0
  42. data/app/controllers/collavre/devices_controller.rb +19 -0
  43. data/app/controllers/collavre/email_verifications_controller.rb +16 -0
  44. data/app/controllers/collavre/emails_controller.rb +11 -0
  45. data/app/controllers/collavre/github_auth_controller.rb +25 -0
  46. data/app/controllers/collavre/google_auth_controller.rb +43 -0
  47. data/app/controllers/collavre/inbox_items_controller.rb +64 -0
  48. data/app/controllers/collavre/invites_controller.rb +27 -0
  49. data/app/controllers/collavre/notion_auth_controller.rb +25 -0
  50. data/app/controllers/collavre/passwords_controller.rb +37 -0
  51. data/app/controllers/collavre/plans_controller.rb +110 -0
  52. data/app/controllers/collavre/sessions_controller.rb +57 -0
  53. data/app/controllers/collavre/topics_controller.rb +58 -0
  54. data/app/controllers/collavre/user_themes_controller.rb +58 -0
  55. data/app/controllers/collavre/users_controller.rb +390 -0
  56. data/app/helpers/collavre/application_helper.rb +4 -0
  57. data/app/helpers/collavre/comments_helper.rb +9 -0
  58. data/app/helpers/collavre/creatives_helper.rb +343 -0
  59. data/app/helpers/collavre/navigation_helper.rb +163 -0
  60. data/app/helpers/collavre/user_themes_helper.rb +4 -0
  61. data/app/javascript/collavre.js +26 -0
  62. data/app/javascript/components/InlineLexicalEditor.jsx +889 -0
  63. data/app/javascript/components/LinkPopup.jsx +112 -0
  64. data/app/javascript/components/creative_tree_row.js +503 -0
  65. data/app/javascript/components/plugins/attachment_cleanup_plugin.jsx +95 -0
  66. data/app/javascript/components/plugins/image_upload_plugin.jsx +162 -0
  67. data/app/javascript/controllers/click_target_controller.js +13 -0
  68. data/app/javascript/controllers/comment_controller.js +162 -0
  69. data/app/javascript/controllers/comments/__tests__/popup_controller.test.js +68 -0
  70. data/app/javascript/controllers/comments/form_controller.js +530 -0
  71. data/app/javascript/controllers/comments/list_controller.js +715 -0
  72. data/app/javascript/controllers/comments/mention_menu_controller.js +41 -0
  73. data/app/javascript/controllers/comments/popup_controller.js +385 -0
  74. data/app/javascript/controllers/comments/presence_controller.js +311 -0
  75. data/app/javascript/controllers/comments/topics_controller.js +338 -0
  76. data/app/javascript/controllers/common_popup_controller.js +55 -0
  77. data/app/javascript/controllers/creatives/drag_drop_controller.js +45 -0
  78. data/app/javascript/controllers/creatives/expansion_controller.js +222 -0
  79. data/app/javascript/controllers/creatives/import_controller.js +116 -0
  80. data/app/javascript/controllers/creatives/row_editor_controller.js +8 -0
  81. data/app/javascript/controllers/creatives/select_mode_controller.js +231 -0
  82. data/app/javascript/controllers/creatives/set_plan_modal_controller.js +107 -0
  83. data/app/javascript/controllers/creatives/tree_controller.js +218 -0
  84. data/app/javascript/controllers/index.js +79 -0
  85. data/app/javascript/controllers/link_creative_controller.js +91 -0
  86. data/app/javascript/controllers/popup_menu_controller.js +82 -0
  87. data/app/javascript/controllers/progress_filter_controller.js +35 -0
  88. data/app/javascript/controllers/reaction_picker_controller.js +107 -0
  89. data/app/javascript/controllers/share_invite_controller.js +15 -0
  90. data/app/javascript/controllers/share_user_search_controller.js +121 -0
  91. data/app/javascript/controllers/tabs_controller.js +43 -0
  92. data/app/javascript/creatives/drag_drop/dom.js +170 -0
  93. data/app/javascript/creatives/drag_drop/event_handlers.js +846 -0
  94. data/app/javascript/creatives/drag_drop/indicator.js +35 -0
  95. data/app/javascript/creatives/drag_drop/operations.js +116 -0
  96. data/app/javascript/creatives/drag_drop/state.js +31 -0
  97. data/app/javascript/creatives/tree_renderer.js +248 -0
  98. data/app/javascript/lib/api/__tests__/queue_manager.test.js +153 -0
  99. data/app/javascript/lib/api/creatives.js +79 -0
  100. data/app/javascript/lib/api/csrf_fetch.js +22 -0
  101. data/app/javascript/lib/api/drag_drop.js +31 -0
  102. data/app/javascript/lib/api/queue_manager.js +423 -0
  103. data/app/javascript/lib/apply_lexical_styles.js +15 -0
  104. data/app/javascript/lib/common_popup.js +195 -0
  105. data/app/javascript/lib/lexical/__tests__/action_text_attachment_node.test.jsx +91 -0
  106. data/app/javascript/lib/lexical/__tests__/attachment_payload.test.js +194 -0
  107. data/app/javascript/lib/lexical/action_text_attachment_node.js +459 -0
  108. data/app/javascript/lib/lexical/attachment_node.jsx +170 -0
  109. data/app/javascript/lib/lexical/attachment_payload.js +293 -0
  110. data/app/javascript/lib/lexical/dom_attachment_utils.js +66 -0
  111. data/app/javascript/lib/lexical/image_node.jsx +159 -0
  112. data/app/javascript/lib/lexical/style_attributes.js +40 -0
  113. data/app/javascript/lib/responsive_images.js +54 -0
  114. data/app/javascript/lib/turbo_stream_actions.js +33 -0
  115. data/app/javascript/lib/utils/markdown.js +23 -0
  116. data/app/javascript/modules/creative_guide.js +53 -0
  117. data/app/javascript/modules/creative_row_editor.js +1841 -0
  118. data/app/javascript/modules/creative_row_swipe.js +43 -0
  119. data/app/javascript/modules/creatives.js +15 -0
  120. data/app/javascript/modules/export_to_markdown.js +34 -0
  121. data/app/javascript/modules/inbox_panel.js +226 -0
  122. data/app/javascript/modules/lexical_inline_editor.jsx +133 -0
  123. data/app/javascript/modules/mention_menu.js +77 -0
  124. data/app/javascript/modules/plans_menu.js +39 -0
  125. data/app/javascript/modules/plans_timeline.js +397 -0
  126. data/app/javascript/modules/share_modal.js +73 -0
  127. data/app/javascript/modules/share_user_popup.js +77 -0
  128. data/app/javascript/modules/slide_view.js +163 -0
  129. data/app/javascript/services/cable.js +32 -0
  130. data/app/javascript/slide_view.js +2 -0
  131. data/app/javascript/utils/caret_position.js +42 -0
  132. data/app/javascript/utils/clipboard.js +40 -0
  133. data/app/jobs/collavre/ai_agent_job.rb +27 -0
  134. data/app/jobs/collavre/inbox_summary_job.rb +24 -0
  135. data/app/jobs/collavre/notion_export_job.rb +30 -0
  136. data/app/jobs/collavre/notion_sync_job.rb +48 -0
  137. data/app/jobs/collavre/permission_cache_cleanup_job.rb +36 -0
  138. data/app/jobs/collavre/permission_cache_job.rb +71 -0
  139. data/app/jobs/collavre/push_notification_job.rb +86 -0
  140. data/app/mailers/collavre/application_mailer.rb +17 -0
  141. data/app/mailers/collavre/creative_mailer.rb +9 -0
  142. data/app/mailers/collavre/email_verification_mailer.rb +20 -0
  143. data/app/mailers/collavre/inbox_mailer.rb +19 -0
  144. data/app/mailers/collavre/invitation_mailer.rb +16 -0
  145. data/app/mailers/collavre/passwords_mailer.rb +10 -0
  146. data/app/models/collavre/activity_log.rb +13 -0
  147. data/app/models/collavre/application_record.rb +5 -0
  148. data/app/models/collavre/calendar_event.rb +20 -0
  149. data/app/models/collavre/comment.rb +307 -0
  150. data/app/models/collavre/comment_presence_store.rb +30 -0
  151. data/app/models/collavre/comment_reaction.rb +11 -0
  152. data/app/models/collavre/comment_read_pointer.rb +26 -0
  153. data/app/models/collavre/contact.rb +23 -0
  154. data/app/models/collavre/creative.rb +413 -0
  155. data/app/models/collavre/creative_expanded_state.rb +11 -0
  156. data/app/models/collavre/creative_share.rb +122 -0
  157. data/app/models/collavre/creative_shares_cache.rb +18 -0
  158. data/app/models/collavre/current.rb +14 -0
  159. data/app/models/collavre/device.rb +13 -0
  160. data/app/models/collavre/email.rb +14 -0
  161. data/app/models/collavre/github_account.rb +10 -0
  162. data/app/models/collavre/github_repository_link.rb +19 -0
  163. data/app/models/collavre/inbox_item.rb +95 -0
  164. data/app/models/collavre/invitation.rb +22 -0
  165. data/app/models/collavre/label.rb +47 -0
  166. data/app/models/collavre/mcp_tool.rb +30 -0
  167. data/app/models/collavre/notion_account.rb +17 -0
  168. data/app/models/collavre/notion_block_link.rb +10 -0
  169. data/app/models/collavre/notion_page_link.rb +19 -0
  170. data/app/models/collavre/plan.rb +20 -0
  171. data/app/models/collavre/session.rb +24 -0
  172. data/app/models/collavre/system_setting.rb +144 -0
  173. data/app/models/collavre/tag.rb +10 -0
  174. data/app/models/collavre/task.rb +10 -0
  175. data/app/models/collavre/task_action.rb +10 -0
  176. data/app/models/collavre/topic.rb +12 -0
  177. data/app/models/collavre/user.rb +174 -0
  178. data/app/models/collavre/user_theme.rb +10 -0
  179. data/app/models/collavre/variation.rb +5 -0
  180. data/app/models/collavre/webauthn_credential.rb +11 -0
  181. data/app/services/collavre/ai_agent_service.rb +193 -0
  182. data/app/services/collavre/ai_client.rb +183 -0
  183. data/app/services/collavre/ai_system_prompt_renderer.rb +38 -0
  184. data/app/services/collavre/auto_theme_generator.rb +198 -0
  185. data/app/services/collavre/comment_link_formatter.rb +60 -0
  186. data/app/services/collavre/comments/action_executor.rb +262 -0
  187. data/app/services/collavre/comments/action_validator.rb +58 -0
  188. data/app/services/collavre/comments/calendar_command.rb +97 -0
  189. data/app/services/collavre/comments/command_processor.rb +37 -0
  190. data/app/services/collavre/comments/mcp_command.rb +109 -0
  191. data/app/services/collavre/comments/mcp_command_builder.rb +32 -0
  192. data/app/services/collavre/creatives/filter_pipeline.rb +196 -0
  193. data/app/services/collavre/creatives/filters/assignee_filter.rb +30 -0
  194. data/app/services/collavre/creatives/filters/base_filter.rb +24 -0
  195. data/app/services/collavre/creatives/filters/comment_filter.rb +21 -0
  196. data/app/services/collavre/creatives/filters/date_filter.rb +58 -0
  197. data/app/services/collavre/creatives/filters/progress_filter.rb +25 -0
  198. data/app/services/collavre/creatives/filters/search_filter.rb +28 -0
  199. data/app/services/collavre/creatives/filters/tag_filter.rb +16 -0
  200. data/app/services/collavre/creatives/importer.rb +47 -0
  201. data/app/services/collavre/creatives/index_query.rb +191 -0
  202. data/app/services/collavre/creatives/path_exporter.rb +131 -0
  203. data/app/services/collavre/creatives/permission_cache_builder.rb +194 -0
  204. data/app/services/collavre/creatives/permission_checker.rb +42 -0
  205. data/app/services/collavre/creatives/plan_tagger.rb +53 -0
  206. data/app/services/collavre/creatives/progress_service.rb +89 -0
  207. data/app/services/collavre/creatives/reorderer.rb +187 -0
  208. data/app/services/collavre/creatives/tree_builder.rb +231 -0
  209. data/app/services/collavre/creatives/tree_formatter.rb +36 -0
  210. data/app/services/collavre/gemini_parent_recommender.rb +77 -0
  211. data/app/services/collavre/github/client.rb +112 -0
  212. data/app/services/collavre/github/pull_request_analyzer.rb +280 -0
  213. data/app/services/collavre/github/pull_request_processor.rb +181 -0
  214. data/app/services/collavre/github/webhook_provisioner.rb +130 -0
  215. data/app/services/collavre/google_calendar_service.rb +149 -0
  216. data/app/services/collavre/link_preview_fetcher.rb +230 -0
  217. data/app/services/collavre/markdown_importer.rb +202 -0
  218. data/app/services/collavre/mcp_service.rb +217 -0
  219. data/app/services/collavre/notion_client.rb +231 -0
  220. data/app/services/collavre/notion_creative_exporter.rb +296 -0
  221. data/app/services/collavre/notion_service.rb +249 -0
  222. data/app/services/collavre/ppt_importer.rb +76 -0
  223. data/app/services/collavre/ruby_llm_interaction_logger.rb +34 -0
  224. data/app/services/collavre/system_events/context_builder.rb +41 -0
  225. data/app/services/collavre/system_events/dispatcher.rb +19 -0
  226. data/app/services/collavre/system_events/router.rb +72 -0
  227. data/app/services/collavre/tools/creative_retrieval_service.rb +138 -0
  228. data/app/views/admin/shared/_tabs.html.erb +4 -0
  229. data/app/views/collavre/comments/_activity_log_details.html.erb +23 -0
  230. data/app/views/collavre/comments/_comment.html.erb +147 -0
  231. data/app/views/collavre/comments/_comments_popup.html.erb +46 -0
  232. data/app/views/collavre/comments/_list.html.erb +10 -0
  233. data/app/views/collavre/comments/_presence_avatars.html.erb +8 -0
  234. data/app/views/collavre/comments/_reaction_picker.html.erb +15 -0
  235. data/app/views/collavre/comments/_read_receipts.html.erb +19 -0
  236. data/app/views/collavre/creatives/_add_button.html.erb +20 -0
  237. data/app/views/collavre/creatives/_delete_button.html.erb +12 -0
  238. data/app/views/collavre/creatives/_github_integration_modal.html.erb +77 -0
  239. data/app/views/collavre/creatives/_import_upload_zone.html.erb +10 -0
  240. data/app/views/collavre/creatives/_inline_edit_form.html.erb +89 -0
  241. data/app/views/collavre/creatives/_mobile_actions_menu.html.erb +24 -0
  242. data/app/views/collavre/creatives/_notion_integration_modal.html.erb +90 -0
  243. data/app/views/collavre/creatives/_set_plan_modal.html.erb +25 -0
  244. data/app/views/collavre/creatives/_share_button.html.erb +55 -0
  245. data/app/views/collavre/creatives/edit.html.erb +4 -0
  246. data/app/views/collavre/creatives/index.html.erb +192 -0
  247. data/app/views/collavre/creatives/new.html.erb +4 -0
  248. data/app/views/collavre/creatives/show.html.erb +29 -0
  249. data/app/views/collavre/creatives/slide_view.html.erb +20 -0
  250. data/app/views/collavre/email_verification_mailer/verify.html.erb +4 -0
  251. data/app/views/collavre/email_verification_mailer/verify.text.erb +2 -0
  252. data/app/views/collavre/emails/index.html.erb +19 -0
  253. data/app/views/collavre/emails/show.html.erb +5 -0
  254. data/app/views/collavre/inbox_items/_item.html.erb +18 -0
  255. data/app/views/collavre/inbox_items/_items.html.erb +3 -0
  256. data/app/views/collavre/inbox_items/_list.html.erb +7 -0
  257. data/app/views/collavre/inbox_items/index.html.erb +1 -0
  258. data/app/views/collavre/inbox_mailer/daily_summary.html.erb +11 -0
  259. data/app/views/collavre/inbox_mailer/daily_summary.text.erb +8 -0
  260. data/app/views/collavre/invitation_mailer/invite.html.erb +5 -0
  261. data/app/views/collavre/invitation_mailer/invite.text.erb +3 -0
  262. data/app/views/collavre/invites/show.html.erb +8 -0
  263. data/app/views/collavre/passwords/edit.html.erb +12 -0
  264. data/app/views/collavre/passwords/new.html.erb +17 -0
  265. data/app/views/collavre/passwords_mailer/reset.html.erb +4 -0
  266. data/app/views/collavre/passwords_mailer/reset.text.erb +2 -0
  267. data/app/views/collavre/sessions/new.html.erb +27 -0
  268. data/app/views/collavre/sessions/providers/_google.html.erb +7 -0
  269. data/app/views/collavre/sessions/providers/_passkey.html.erb +3 -0
  270. data/app/views/collavre/shared/_link_creative_modal.html.erb +6 -0
  271. data/app/views/collavre/shared/_navigation.html.erb +37 -0
  272. data/app/views/collavre/shared/navigation/_help_button.html.erb +1 -0
  273. data/app/views/collavre/shared/navigation/_inbox_button.html.erb +5 -0
  274. data/app/views/collavre/shared/navigation/_mobile_inbox_button.html.erb +3 -0
  275. data/app/views/collavre/shared/navigation/_mobile_plans_button.html.erb +1 -0
  276. data/app/views/collavre/shared/navigation/_panels.html.erb +18 -0
  277. data/app/views/collavre/shared/navigation/_plans_button.html.erb +3 -0
  278. data/app/views/collavre/shared/navigation/_search_form.html.erb +6 -0
  279. data/app/views/collavre/user_themes/index.html.erb +75 -0
  280. data/app/views/collavre/users/_app_header.html.erb +5 -0
  281. data/app/views/collavre/users/_contact_management.html.erb +77 -0
  282. data/app/views/collavre/users/_id_pwd_fields.html.erb +27 -0
  283. data/app/views/collavre/users/edit_ai.html.erb +79 -0
  284. data/app/views/collavre/users/edit_password.html.erb +27 -0
  285. data/app/views/collavre/users/index.html.erb +80 -0
  286. data/app/views/collavre/users/new.html.erb +33 -0
  287. data/app/views/collavre/users/new_ai.html.erb +87 -0
  288. data/app/views/collavre/users/passkeys.html.erb +43 -0
  289. data/app/views/collavre/users/show.html.erb +143 -0
  290. data/app/views/inbox/badge_component/_count.html.erb +2 -0
  291. data/app/views/layouts/collavre/slide.html.erb +29 -0
  292. data/config/locales/comments.en.yml +114 -0
  293. data/config/locales/comments.ko.yml +110 -0
  294. data/config/locales/contacts.en.yml +16 -0
  295. data/config/locales/contacts.ko.yml +16 -0
  296. data/config/locales/creatives.en.yml +183 -0
  297. data/config/locales/creatives.ko.yml +164 -0
  298. data/config/locales/invites.en.yml +19 -0
  299. data/config/locales/invites.ko.yml +17 -0
  300. data/config/locales/notifications.en.yml +8 -0
  301. data/config/locales/notifications.ko.yml +8 -0
  302. data/config/locales/plans.en.yml +12 -0
  303. data/config/locales/plans.ko.yml +19 -0
  304. data/config/locales/themes.en.yml +29 -0
  305. data/config/locales/themes.ko.yml +27 -0
  306. data/config/locales/users.en.yml +151 -0
  307. data/config/locales/users.ko.yml +146 -0
  308. data/config/routes.rb +92 -0
  309. data/db/migrate/20241201000000_create_notion_integrations.rb +29 -0
  310. data/db/migrate/20250128110017_create_creatives.rb +11 -0
  311. data/db/migrate/20250128120122_create_users.rb +11 -0
  312. data/db/migrate/20250128120123_create_sessions.rb +11 -0
  313. data/db/migrate/20250128123633_create_subscribers.rb +10 -0
  314. data/db/migrate/20250312000000_create_notion_block_links.rb +16 -0
  315. data/db/migrate/20250312010000_allow_multiple_notion_blocks_per_creative.rb +5 -0
  316. data/db/migrate/20250522115048_remove_name_from_creatives.rb +5 -0
  317. data/db/migrate/20250522190651_add_parent_id_to_creatives.rb +5 -0
  318. data/db/migrate/20250523133100_rename_inventory_count_to_progress.rb +13 -0
  319. data/db/migrate/20250523133101_add_sequence_to_creatives.rb +5 -0
  320. data/db/migrate/20250525205100_add_user_id_to_creatives.rb +10 -0
  321. data/db/migrate/20250527014217_create_creative_shares.rb +12 -0
  322. data/db/migrate/20250528142349_add_origin_id_to_creatives.rb +5 -0
  323. data/db/migrate/20250530060200_create_tags.rb +9 -0
  324. data/db/migrate/20250531105150_add_value_to_tags.rb +5 -0
  325. data/db/migrate/20250531140142_create_labels.rb +12 -0
  326. data/db/migrate/20250531140145_change_tag_to_label_reference.rb +6 -0
  327. data/db/migrate/20250601000000_create_comments.rb +10 -0
  328. data/db/migrate/20250601061830_drop_plans_and_variations.rb +6 -0
  329. data/db/migrate/20250604122600_add_owner_to_labels.rb +5 -0
  330. data/db/migrate/20250606000000_rename_email_address_to_email_in_users.rb +7 -0
  331. data/db/migrate/20250606150329_create_creative_hierarchies.rb +16 -0
  332. data/db/migrate/20250610142000_create_creative_expanded_states.rb +11 -0
  333. data/db/migrate/20250611030138_create_invitations.rb +15 -0
  334. data/db/migrate/20250611105524_create_inbox_items.rb +14 -0
  335. data/db/migrate/20250612150000_update_permissions.rb +43 -0
  336. data/db/migrate/20250612232913_add_email_verified_at_to_users.rb +5 -0
  337. data/db/migrate/20250616065905_add_avatar_to_users.rb +5 -0
  338. data/db/migrate/20250617092111_add_display_level_to_users.rb +5 -0
  339. data/db/migrate/20250620004558_create_emails.rb +13 -0
  340. data/db/migrate/20250621000000_create_comment_read_pointers.rb +11 -0
  341. data/db/migrate/20250622000000_add_completion_mark_to_users.rb +5 -0
  342. data/db/migrate/20250623000000_add_theme_to_users.rb +5 -0
  343. data/db/migrate/20250624000000_add_name_to_users.rb +18 -0
  344. data/db/migrate/20250714190000_create_devices.rb +17 -0
  345. data/db/migrate/20250715120000_add_notifications_enabled_to_users.rb +5 -0
  346. data/db/migrate/20250823000000_add_no_access_permission.rb +25 -0
  347. data/db/migrate/20250826000000_add_calendar_id_to_users.rb +5 -0
  348. data/db/migrate/20250827000000_add_google_oauth_tokens_to_users.rb +8 -0
  349. data/db/migrate/20250827061238_add_timezone_to_users.rb +5 -0
  350. data/db/migrate/20250828000000_create_calendar_events.rb +16 -0
  351. data/db/migrate/20250828060000_add_creative_to_calendar_events.rb +5 -0
  352. data/db/migrate/20250830141052_add_locale_to_users.rb +5 -0
  353. data/db/migrate/20250830141101_add_message_key_to_inbox_items.rb +6 -0
  354. data/db/migrate/20250902025423_remove_description_and_featured_image_from_creatives.rb +6 -0
  355. data/db/migrate/20250910000000_add_private_to_comments.rb +5 -0
  356. data/db/migrate/20250910105640_migrate_write_permissions_to_admin.rb +17 -0
  357. data/db/migrate/20250911084338_backfill_creative_in_comment_inbox_items.rb +27 -0
  358. data/db/migrate/20250923002959_deduplicate_device_fcm_tokens.rb +25 -0
  359. data/db/migrate/20250924000000_add_system_admin_to_users.rb +6 -0
  360. data/db/migrate/20250925000000_create_github_integrations.rb +26 -0
  361. data/db/migrate/20250927000000_add_webhook_secret_to_github_repository_links.rb +29 -0
  362. data/db/migrate/20250928000000_add_action_and_approver_to_comments.rb +6 -0
  363. data/db/migrate/20250928010000_add_action_execution_tracking_to_comments.rb +6 -0
  364. data/db/migrate/20250928105957_add_github_gemini_prompt_to_creatives.rb +5 -0
  365. data/db/migrate/20250929000000_add_comment_and_creative_refs_to_inbox_items.rb +6 -0
  366. data/db/migrate/20251001000001_create_contacts.rb +71 -0
  367. data/db/migrate/20251002000000_add_shared_by_to_creative_shares.rb +14 -0
  368. data/db/migrate/20251124120902_add_ai_fields_to_users.rb +7 -0
  369. data/db/migrate/20251124122218_add_created_by_id_to_users.rb +5 -0
  370. data/db/migrate/20251124124521_add_llm_api_key_to_users.rb +5 -0
  371. data/db/migrate/20251124130000_add_searchable_to_users.rb +6 -0
  372. data/db/migrate/20251125072705_migrate_linked_creative_children_to_origin.rb +28 -0
  373. data/db/migrate/20251126040752_add_description_to_creatives.rb +75 -0
  374. data/db/migrate/20251127000000_move_comments_from_linked_creatives_to_origins.rb +18 -0
  375. data/db/migrate/20251201073823_add_tools_to_users.rb +5 -0
  376. data/db/migrate/20251202062715_create_mcp_tools.rb +16 -0
  377. data/db/migrate/20251204125754_create_tasks_and_task_actions.rb +23 -0
  378. data/db/migrate/20251204125756_add_routing_expression_to_users.rb +5 -0
  379. data/db/migrate/20251204161133_set_default_routing_expression_for_ai_agents.rb +13 -0
  380. data/db/migrate/20251211033025_nullify_self_referencing_origins.rb +9 -0
  381. data/db/migrate/20251211080040_create_topics_and_add_topic_to_comments.rb +15 -0
  382. data/db/migrate/20251215143500_create_activity_logs.rb +16 -0
  383. data/db/migrate/20251222125727_create_webauthn_credentials.rb +14 -0
  384. data/db/migrate/20251222125839_add_webauthn_id_to_users.rb +5 -0
  385. data/db/migrate/20251223022315_create_comment_reactions.rb +13 -0
  386. data/db/migrate/20251223072625_create_user_themes.rb +11 -0
  387. data/db/migrate/20251230074456_add_creative_id_to_labels.rb +5 -0
  388. data/db/migrate/20251230113607_refactor_labels.rb +38 -0
  389. data/db/migrate/20251231010012_backfill_tags_for_labels.rb +15 -0
  390. data/db/migrate/20251231013234_drop_subscribers.rb +9 -0
  391. data/db/migrate/20260106090544_allow_null_user_id_in_creative_shares.rb +6 -0
  392. data/db/migrate/20260106160643_create_system_settings.rb +11 -0
  393. data/db/migrate/20260116000000_create_creative_shares_cache.rb +15 -0
  394. data/db/migrate/20260116000001_populate_creative_shares_cache.rb +23 -0
  395. data/db/migrate/20260119022933_make_source_share_id_nullable_in_creative_shares_caches.rb +7 -0
  396. data/db/migrate/20260119023446_populate_owner_cache_entries.rb +25 -0
  397. data/db/migrate/20260119100000_add_account_lockout_to_users.rb +6 -0
  398. data/db/migrate/20260119110000_add_last_active_at_to_sessions.rb +6 -0
  399. data/db/migrate/20260120045354_encrypt_oauth_tokens.rb +60 -0
  400. data/db/migrate/20260120162259_remove_fk_from_creative_shares_caches.rb +7 -0
  401. data/db/migrate/20260120163856_remove_timestamps_from_creative_shares_caches.rb +6 -0
  402. data/lib/collavre/configuration.rb +14 -0
  403. data/lib/collavre/engine.rb +77 -0
  404. data/lib/collavre/user_extensions.rb +29 -0
  405. data/lib/collavre/version.rb +3 -0
  406. data/lib/collavre.rb +26 -0
  407. data/lib/generators/collavre/install/install_generator.rb +105 -0
  408. data/lib/generators/collavre/install/templates/build.cjs.tt +100 -0
  409. data/lib/tasks/collavre_assets.rake +15 -0
  410. metadata +591 -0
@@ -0,0 +1,217 @@
1
+ module Collavre
2
+ require "digest"
3
+ require "set"
4
+
5
+ class McpService
6
+ # --- Registration Logic (from MetaToolService) ---
7
+
8
+ def self.register_tool_from_source(source_code)
9
+ # Extract tool name for logging context
10
+ tool_name_match = source_code.match(/tool_name\s+["'](.+?)["']/)
11
+ tool_name = tool_name_match ? tool_name_match[1] : "unknown_tool"
12
+
13
+ before_call = proc do |tool_instance, method_name, args|
14
+ # Store args for after_call access if needed, or just log start
15
+ # Using thread local to pass data to after_call if we want to correlate exact timing or args
16
+ Thread.current[:mcp_tool_args_stack] ||= []
17
+ Thread.current[:mcp_tool_args_stack].push(args)
18
+ end
19
+
20
+ after_call = proc do |tool_instance, method_name, result|
21
+ # Retrieve args
22
+ args = Thread.current[:mcp_tool_args_stack]&.pop || {}
23
+
24
+ # Create activity log
25
+ # We need a user to attribute this to.
26
+ # If executed in a background job (Task), Current.user is set.
27
+ # If executed via API, Current.user is set.
28
+ user = Current.user
29
+ creative = tool_instance&.try(:creative_context) rescue nil # Assuming some way to get context if needed, or nil
30
+
31
+ ActivityLog.create!(
32
+ activity: "tool_execution",
33
+ user: user,
34
+ creative: creative, # Optional: if we can link it back to a creative
35
+ log: {
36
+ tool_name: tool_name,
37
+ method: method_name,
38
+ args: args,
39
+ result: result
40
+ }
41
+ )
42
+ rescue => e
43
+ Rails.logger.error("Failed to log tool activity: #{e.message}")
44
+ end
45
+
46
+ result = ::Tools::MetaToolWriteService.new.register_tool_from_source(
47
+ source: source_code,
48
+ before_call: before_call,
49
+ after_call: after_call
50
+ )
51
+ puts("Registered tool: #{result}")
52
+
53
+ if result[:error]
54
+ error_msg = "Failed to register tool: #{result[:error]}"
55
+ Rails.logger.error(error_msg)
56
+ raise error_msg
57
+ end
58
+ rescue => e
59
+ Rails.logger.error("Failed to register tool from source: #{e.message}")
60
+ raise e
61
+ end
62
+
63
+ def self.filter_tools(tools, user)
64
+ return [] if tools.blank?
65
+
66
+ # Identify dynamic tools (user-defined) vs system tools.
67
+ # Tools can be objects (FastMcp::Tool) or Hashes (from MetaToolService)
68
+ registered_names = tools.map do |tool|
69
+ if tool.respond_to?(:tool_name)
70
+ tool.tool_name
71
+ elsif tool.is_a?(Hash)
72
+ tool[:name] || tool["name"]
73
+ end
74
+ end
75
+
76
+ # Check strict loading? No, simple where is fine.
77
+ dynamic_tools = McpTool.where(name: registered_names)
78
+ dynamic_tool_names = dynamic_tools.pluck(:name).to_set
79
+
80
+ # If user is present, find their owned tools
81
+ user_owned_tool_names = if user
82
+ dynamic_tools
83
+ .joins(:creative)
84
+ .where(creatives: { user_id: user.id })
85
+ .pluck(:name)
86
+ .to_set
87
+ else
88
+ Set.new
89
+ end
90
+
91
+ tools.select do |tool|
92
+ name = if tool.respond_to?(:tool_name)
93
+ tool.tool_name
94
+ elsif tool.is_a?(Hash)
95
+ tool[:name] || tool["name"]
96
+ else
97
+ nil
98
+ end
99
+ if dynamic_tool_names.include?(name)
100
+ # It is a dynamic tool; user must own it.
101
+ user_owned_tool_names.include?(name)
102
+ else
103
+ # It is a system tool (not in McpTool database); allow it.
104
+ true
105
+ end
106
+ end
107
+ end
108
+
109
+ def self.load_active_tools
110
+ McpTool.active.find_each do |tool|
111
+ register_tool_from_source(tool.source_code)
112
+ end
113
+ end
114
+
115
+ def self.delete_tool(tool_name)
116
+ result = ::Tools::MetaToolWriteService.new.delete_tool(tool_name)
117
+
118
+ if result[:error]
119
+ Rails.logger.error("Failed to delete tool #{tool_name}: #{result[:error]}")
120
+ end
121
+ rescue => e
122
+ Rails.logger.error("Failed to delete tool #{tool_name}: #{e.message}")
123
+ end
124
+
125
+ # --- Creative Parsing Logic (from MetaToolWriteService) ---
126
+
127
+ def update_from_creative(input_creative)
128
+ creative = input_creative.effective_origin
129
+ return unless creative.description.present?
130
+
131
+ # Parse HTML to find code blocks
132
+ doc = Nokogiri::HTML.fragment(creative.description)
133
+
134
+ # Track found tools to identify removals
135
+ found_tool_names = []
136
+
137
+ # Find all code blocks.
138
+ # Lexical uses <pre class="lexical-code-block">.
139
+ # Standard markdown often uses <code>.
140
+ doc.css("pre.lexical-code-block, code").each do |node|
141
+ # Create a copy to manipulate
142
+ working_node = node.dup
143
+
144
+ # Replace <br> tags with newlines
145
+ working_node.search("br").each { |br| br.replace("\n") }
146
+
147
+ code = working_node.text
148
+
149
+ # Check if it looks like a tool definition
150
+ if code.include?("extend ToolMeta")
151
+ tool_name = process_tool_definition(creative, code)
152
+ found_tool_names << tool_name if tool_name
153
+ end
154
+ end
155
+
156
+ # Remove tools that are no longer in the description
157
+ # Ensure we look at the effective origin's tools
158
+ creative.mcp_tools.where.not(name: found_tool_names).destroy_all
159
+ end
160
+
161
+ private
162
+
163
+ def process_tool_definition(input_creative, code)
164
+ creative = input_creative.effective_origin
165
+ # Extract tool name using regex
166
+ tool_name_match = code.match(/tool_name\s+["'](.+?)["']/)
167
+ return unless tool_name_match
168
+
169
+ tool_name = tool_name_match[1]
170
+
171
+ mcp_tool = McpTool.find_or_initialize_by(creative: creative, name: tool_name)
172
+
173
+ # Calculate checksum to detect changes
174
+ new_checksum = Digest::SHA256.hexdigest(code)
175
+
176
+ if mcp_tool.new_record? || mcp_tool.checksum != new_checksum
177
+ # Unregister old tool if it exists (source changed)
178
+ if !mcp_tool.new_record?
179
+ McpService.delete_tool(tool_name)
180
+ end
181
+
182
+ mcp_tool.source_code = code
183
+ mcp_tool.checksum = new_checksum
184
+ mcp_tool.approved_at = nil # Reset approval status on change
185
+
186
+ # Extract description
187
+ desc_match = code.match(/tool_description\s+["'](.+?)["']/)
188
+ mcp_tool.description = desc_match[1] if desc_match
189
+
190
+ if mcp_tool.save
191
+ notify_approval_needed(creative, mcp_tool)
192
+ end
193
+ end
194
+
195
+ tool_name
196
+ end
197
+
198
+ def notify_approval_needed(creative, tool)
199
+ message = I18n.t("collavre.inbox.tool_approval_needed", tool_name: tool.name)
200
+
201
+ # Create a comment with action payload for approval
202
+ action_payload = {
203
+ action: "approve_tool",
204
+ tool_name: tool.name
205
+ }
206
+
207
+ Comment.create(
208
+ creative: creative,
209
+ content: message,
210
+ user: nil, # System message
211
+ approver: creative.user, # The creative owner should approve
212
+ action: JSON.pretty_generate(action_payload),
213
+ private: false
214
+ )
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,231 @@
1
+ module Collavre
2
+ class NotionClient
3
+ BASE_URL = "https://api.notion.com/v1"
4
+ API_VERSION = "2022-06-28"
5
+
6
+ def initialize(account)
7
+ @account = account
8
+ @token = account.token
9
+ end
10
+
11
+ def search_pages(query: nil, start_cursor: nil, page_size: 10)
12
+ Rails.logger.info("NotionClient: Searching for pages with query: #{query}, page_size: #{page_size}")
13
+
14
+ body = {
15
+ filter: { property: "object", value: "page" },
16
+ page_size: page_size
17
+ }
18
+ body[:query] = query if query.present?
19
+ body[:start_cursor] = start_cursor if start_cursor.present?
20
+
21
+ Rails.logger.info("NotionClient: Search request body: #{body.to_json}")
22
+ result = post("search", body)
23
+ Rails.logger.info("NotionClient: Search returned #{result["results"]&.length || 0} results")
24
+ result
25
+ end
26
+
27
+ def get_page(page_id)
28
+ get("pages/#{format_id(page_id)}")
29
+ end
30
+
31
+ def create_page(parent_id:, title:, blocks: [])
32
+ Rails.logger.info("NotionClient: Creating page with #{blocks.length} blocks")
33
+
34
+ # Notion limits page creation to 100 blocks, so create with minimal content first
35
+ body = {
36
+ parent: { page_id: parent_id },
37
+ properties: {
38
+ title: {
39
+ title: [ { text: { content: title } } ]
40
+ }
41
+ },
42
+ children: blocks.length > 100 ? [] : blocks
43
+ }
44
+
45
+ response = post("pages", body)
46
+
47
+ # If we have more than 100 blocks, add them in batches after page creation
48
+ if blocks.length > 100
49
+ Rails.logger.info("NotionClient: Adding #{blocks.length} blocks in batches (100 per batch)")
50
+ page_id = response["id"]
51
+ Rails.logger.info("NotionClient: Created page ID: #{page_id}")
52
+
53
+ # Give Notion a moment to fully create the page
54
+ sleep(1)
55
+
56
+ # Add blocks in batches of 100
57
+ blocks.each_slice(100).with_index do |block_batch, index|
58
+ Rails.logger.info("NotionClient: Adding batch #{index + 1} with #{block_batch.length} blocks")
59
+ append_blocks(page_id, block_batch)
60
+ Rails.logger.info("NotionClient: Successfully added batch #{index + 1}")
61
+ end
62
+ end
63
+
64
+ response
65
+ end
66
+
67
+ def update_page(page_id, properties: {}, blocks: nil)
68
+ body = { properties: properties }
69
+ response = patch("pages/#{format_id(page_id)}", body)
70
+
71
+ if blocks.present?
72
+ replace_page_blocks(page_id, blocks)
73
+ end
74
+
75
+ response
76
+ end
77
+
78
+ def get_page_blocks(page_id, start_cursor: nil, page_size: 100)
79
+ params = { page_size: page_size }
80
+ params[:start_cursor] = start_cursor if start_cursor.present?
81
+
82
+ get("blocks/#{format_id(page_id)}/children", params)
83
+ end
84
+
85
+ def replace_page_blocks(page_id, blocks)
86
+ Rails.logger.info("NotionClient: Replacing page blocks with #{blocks.length} blocks")
87
+
88
+ # First, get existing blocks
89
+ existing_blocks = get_page_blocks(page_id)
90
+
91
+ # Delete existing blocks
92
+ existing_blocks.dig("results")&.each do |block|
93
+ delete_block(block["id"])
94
+ end
95
+
96
+ # Add new blocks in batches of 100
97
+ if blocks.any?
98
+ blocks.each_slice(100).with_index do |block_batch, index|
99
+ Rails.logger.info("NotionClient: Adding replacement batch #{index + 1} with #{block_batch.length} blocks")
100
+ append_blocks(page_id, block_batch)
101
+ end
102
+ end
103
+ end
104
+
105
+ def append_blocks(page_id, blocks)
106
+ # Notion also limits append operations to 100 blocks
107
+ if blocks.length > 100
108
+ Rails.logger.info("NotionClient: Appending #{blocks.length} blocks in batches")
109
+ aggregated_results = []
110
+
111
+ blocks.each_slice(100).with_index do |block_batch, index|
112
+ Rails.logger.info("NotionClient: Appending batch #{index + 1} with #{block_batch.length} blocks")
113
+ response = patch("blocks/#{format_id(page_id)}/children", { children: block_batch })
114
+
115
+ if response.is_a?(Hash)
116
+ batch_results = response.fetch("results", [])
117
+ aggregated_results.concat(batch_results) if batch_results.any?
118
+ end
119
+ end
120
+
121
+ aggregated_results.any? ? { "results" => aggregated_results } : nil
122
+ else
123
+ patch("blocks/#{format_id(page_id)}/children", { children: blocks })
124
+ end
125
+ end
126
+
127
+ def delete_block(block_id)
128
+ delete("blocks/#{format_id(block_id)}")
129
+ end
130
+
131
+ def get_workspace
132
+ get("users/me")
133
+ end
134
+
135
+ private
136
+
137
+ def get(path, params = {})
138
+ url = "#{BASE_URL}/#{path}"
139
+ url += "?#{params.to_query}" if params.any?
140
+
141
+ response = HTTParty.get(
142
+ url,
143
+ headers: headers,
144
+ timeout: 30
145
+ )
146
+
147
+ handle_response(response)
148
+ end
149
+
150
+ def post(path, body)
151
+ response = HTTParty.post(
152
+ "#{BASE_URL}/#{path}",
153
+ headers: headers,
154
+ body: body.to_json,
155
+ timeout: 30
156
+ )
157
+
158
+ handle_response(response)
159
+ end
160
+
161
+ def patch(path, body)
162
+ response = HTTParty.patch(
163
+ "#{BASE_URL}/#{path}",
164
+ headers: headers,
165
+ body: body.to_json,
166
+ timeout: 30
167
+ )
168
+
169
+ handle_response(response)
170
+ end
171
+
172
+ def delete(path)
173
+ response = HTTParty.delete(
174
+ "#{BASE_URL}/#{path}",
175
+ headers: headers,
176
+ timeout: 30
177
+ )
178
+
179
+ handle_response(response)
180
+ end
181
+
182
+ def headers
183
+ {
184
+ "Authorization" => "Bearer #{@token}",
185
+ "Notion-Version" => API_VERSION,
186
+ "Content-Type" => "application/json"
187
+ }
188
+ end
189
+
190
+ def format_id(id)
191
+ # Keep dashes in UUIDs - Notion API expects them
192
+ id.to_s
193
+ end
194
+
195
+ def handle_response(response)
196
+ Rails.logger.info("Notion API Response: #{response.code} for #{response.request.last_uri}")
197
+ Rails.logger.debug("Notion API Response Body: #{response.body}")
198
+
199
+ case response.code
200
+ when 200, 201
201
+ response.parsed_response
202
+ when 400
203
+ Rails.logger.error("Notion API 400 error: #{response.body}")
204
+ raise NotionError, "Bad request: #{response.parsed_response}"
205
+ when 401
206
+ Rails.logger.error("Notion API 401 error: #{response.body}")
207
+ raise NotionAuthError, "Unauthorized: Token may be expired"
208
+ when 403
209
+ Rails.logger.error("Notion API 403 error: #{response.body}")
210
+ raise NotionError, "Forbidden: Insufficient permissions"
211
+ when 404
212
+ Rails.logger.error("Notion API 404 error: #{response.body}")
213
+ raise NotionError, "Resource not found"
214
+ when 429
215
+ Rails.logger.error("Notion API 429 error: #{response.body}")
216
+ raise NotionRateLimitError, "Rate limit exceeded"
217
+ else
218
+ Rails.logger.error("Notion API error: #{response.code} #{response.body}")
219
+ raise NotionError, "API error: #{response.code}"
220
+ end
221
+ rescue HTTParty::Error, SocketError, Timeout::Error => e
222
+ Rails.logger.error("Notion API connection error: #{e.message}")
223
+ raise NotionConnectionError, "Connection failed: #{e.message}"
224
+ end
225
+ end
226
+
227
+ class NotionError < StandardError; end
228
+ class NotionAuthError < NotionError; end
229
+ class NotionRateLimitError < NotionError; end
230
+ class NotionConnectionError < NotionError; end
231
+ end