collavre 0.22.0 → 0.23.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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/app/assets/stylesheets/collavre/actiontext.css +251 -90
  4. data/app/assets/stylesheets/collavre/code_highlight.css +7 -201
  5. data/app/assets/stylesheets/collavre/comments_popup.css +118 -61
  6. data/app/assets/stylesheets/collavre/creatives.css +11 -2
  7. data/app/assets/stylesheets/collavre/modal_dialog.css +32 -0
  8. data/app/assets/stylesheets/collavre/tables.css +91 -0
  9. data/app/channels/collavre/inbox_badge_channel.rb +30 -0
  10. data/app/controllers/collavre/api/v1/mobile/agent_events_controller.rb +224 -0
  11. data/app/controllers/collavre/api/v1/mobile/base_controller.rb +95 -0
  12. data/app/controllers/collavre/api/v1/mobile/devices_controller.rb +31 -0
  13. data/app/controllers/collavre/api/v1/mobile/voice_commands_controller.rb +25 -0
  14. data/app/controllers/collavre/creatives_controller.rb +16 -5
  15. data/app/controllers/collavre/tasks_controller.rb +13 -4
  16. data/app/controllers/collavre/topics_controller.rb +49 -1
  17. data/app/controllers/collavre/typo_corrections_controller.rb +39 -0
  18. data/app/controllers/concerns/collavre/users_controller/profile_and_settings.rb +16 -1
  19. data/app/controllers/concerns/collavre/users_controller/registration.rb +41 -1
  20. data/app/helpers/collavre/application_helper.rb +1 -0
  21. data/app/javascript/collavre.js +2 -0
  22. data/app/javascript/components/ImageResizer.jsx +9 -3
  23. data/app/javascript/components/InlineLexicalEditor.jsx +155 -38
  24. data/app/javascript/components/creative_tree_row.js +20 -3
  25. data/app/javascript/components/plugins/list_tab_indent_plugin.jsx +16 -0
  26. data/app/javascript/components/plugins/table_hover_actions_plugin.jsx +405 -0
  27. data/app/javascript/controllers/__tests__/inbox_badge_controller.test.js +73 -0
  28. data/app/javascript/controllers/comment_controller.js +5 -4
  29. data/app/javascript/controllers/comment_version_controller.js +2 -1
  30. data/app/javascript/controllers/comments/__tests__/form_controller_double_submit.test.js +159 -0
  31. data/app/javascript/controllers/comments/__tests__/presence_controller.test.js +3 -2
  32. data/app/javascript/controllers/comments/__tests__/topics_controller_delete.test.js +94 -0
  33. data/app/javascript/controllers/comments/form_controller.js +21 -5
  34. data/app/javascript/controllers/comments/list_controller.js +18 -17
  35. data/app/javascript/controllers/comments/presence_controller.js +2 -1
  36. data/app/javascript/controllers/comments/topics_controller.js +14 -8
  37. data/app/javascript/controllers/creatives/__tests__/tree_controller.test.js +150 -0
  38. data/app/javascript/controllers/creatives/import_controller.js +2 -1
  39. data/app/javascript/controllers/creatives/select_mode_controller.js +2 -1
  40. data/app/javascript/controllers/creatives/tree_controller.js +142 -1
  41. data/app/javascript/controllers/image_lightbox_controller.js +2 -1
  42. data/app/javascript/controllers/inbox_badge_controller.js +33 -0
  43. data/app/javascript/controllers/index.js +4 -1
  44. data/app/javascript/controllers/share_modal_controller.js +4 -3
  45. data/app/javascript/controllers/topic_search_controller.js +2 -1
  46. data/app/javascript/creatives/drag_drop/event_handlers.js +14 -5
  47. data/app/javascript/creatives/topic_move_members_popup.js +156 -0
  48. data/app/javascript/creatives/tree_renderer.js +11 -0
  49. data/app/javascript/lib/__tests__/turbo_confirm.test.js +81 -0
  50. data/app/javascript/lib/__tests__/typo_correction.test.js +192 -0
  51. data/app/javascript/lib/api/__tests__/api_error.test.js +96 -0
  52. data/app/javascript/lib/api/__tests__/queue_manager.test.js +88 -1
  53. data/app/javascript/lib/api/api_error.js +108 -0
  54. data/app/javascript/lib/api/queue_manager.js +38 -4
  55. data/app/javascript/lib/common_popup.js +18 -5
  56. data/app/javascript/lib/editor/__tests__/code_edit_view_token_parity.test.js +121 -0
  57. data/app/javascript/lib/editor/__tests__/code_language_roundtrip.test.js +152 -0
  58. data/app/javascript/lib/editor/__tests__/code_languages.test.js +93 -0
  59. data/app/javascript/lib/editor/code_languages.js +173 -0
  60. data/app/javascript/lib/editor/code_token_theme.js +41 -0
  61. data/app/javascript/lib/lexical/__tests__/image_focus.test.js +139 -0
  62. data/app/javascript/lib/lexical/__tests__/list_tab_indent.test.js +633 -0
  63. data/app/javascript/lib/lexical/__tests__/markdown_serialize.test.js +627 -0
  64. data/app/javascript/lib/lexical/__tests__/minimize_html.test.js +20 -1
  65. data/app/javascript/lib/lexical/__tests__/selection_boundary.test.js +88 -0
  66. data/app/javascript/lib/lexical/__tests__/table_transformer.test.js +163 -0
  67. data/app/javascript/lib/lexical/__tests__/trailing_paragraph.test.js +104 -0
  68. data/app/javascript/lib/lexical/list_tab_indent.js +210 -0
  69. data/app/javascript/lib/lexical/markdown_serialize.js +320 -0
  70. data/app/javascript/lib/lexical/selection_boundary.js +58 -0
  71. data/app/javascript/lib/lexical/table_transformer.js +182 -0
  72. data/app/javascript/lib/lexical/trailing_paragraph.js +29 -0
  73. data/app/javascript/lib/turbo_confirm.js +46 -0
  74. data/app/javascript/lib/typo_correction.js +146 -0
  75. data/app/javascript/lib/utils/__tests__/confirm_dialog.test.js +88 -0
  76. data/app/javascript/lib/utils/__tests__/dialog.test.js +92 -0
  77. data/app/javascript/lib/utils/__tests__/markdown.test.js +153 -0
  78. data/app/javascript/lib/utils/__tests__/sanitize_description.test.js +68 -0
  79. data/app/javascript/lib/utils/__tests__/table_download.test.js +93 -0
  80. data/app/javascript/lib/utils/confirm_dialog.js +10 -0
  81. data/app/javascript/lib/utils/dialog.js +300 -0
  82. data/app/javascript/lib/utils/markdown.js +154 -67
  83. data/app/javascript/lib/utils/sanitize_description.js +31 -0
  84. data/app/javascript/lib/utils/table_download.js +15 -0
  85. data/app/javascript/modules/__tests__/typo_corrector.test.js +365 -0
  86. data/app/javascript/modules/creative_row_editor.js +110 -70
  87. data/app/javascript/modules/export_to_markdown.js +2 -1
  88. data/app/javascript/modules/lexical_inline_editor.jsx +2 -1
  89. data/app/javascript/modules/slide_view.js +11 -2
  90. data/app/javascript/modules/typo_corrector.js +534 -0
  91. data/app/jobs/collavre/ai_agent_job.rb +7 -4
  92. data/app/jobs/collavre/compress_job.rb +6 -2
  93. data/app/models/collavre/comment/broadcastable.rb +46 -7
  94. data/app/models/collavre/comment/notifiable.rb +14 -4
  95. data/app/models/collavre/comment.rb +79 -31
  96. data/app/models/collavre/creative/describable.rb +89 -10
  97. data/app/models/collavre/task.rb +15 -0
  98. data/app/models/collavre/user.rb +57 -1
  99. data/app/services/collavre/ai_client.rb +28 -10
  100. data/app/services/collavre/auto_theme_generator.rb +1 -1
  101. data/app/services/collavre/creatives/index_query.rb +85 -16
  102. data/app/services/collavre/creatives/tree_builder.rb +2 -1
  103. data/app/services/collavre/gemini_parent_recommender.rb +1 -1
  104. data/app/services/collavre/inbox_reply_service.rb +5 -0
  105. data/app/services/collavre/markdown_converter.rb +13 -3
  106. data/app/services/collavre/mobile/event_summarizer.rb +40 -0
  107. data/app/services/collavre/orchestration/agent_orchestrator.rb +33 -7
  108. data/app/services/collavre/orchestration/arbiter.rb +16 -0
  109. data/app/services/collavre/orchestration/matcher.rb +79 -4
  110. data/app/services/collavre/orchestration/policy_resolver.rb +4 -3
  111. data/app/services/collavre/orchestration/stuck_detector.rb +141 -34
  112. data/app/services/collavre/tools/creative_batch_service.rb +3 -2
  113. data/app/services/collavre/tools/creative_create_service.rb +8 -8
  114. data/app/services/collavre/tools/creative_update_service.rb +23 -8
  115. data/app/services/collavre/typo_corrector.rb +188 -0
  116. data/app/views/collavre/comments/_comment.html.erb +5 -0
  117. data/app/views/collavre/comments/_comments_popup.html.erb +14 -1
  118. data/app/views/collavre/creatives/_inline_edit_form.html.erb +1 -0
  119. data/app/views/collavre/creatives/_topic_move_members_modal.html.erb +42 -0
  120. data/app/views/collavre/creatives/index.html.erb +14 -1
  121. data/app/views/collavre/creatives/slide_view.html.erb +1 -1
  122. data/app/views/collavre/users/show.html.erb +3 -0
  123. data/app/views/collavre/users/typo_correction.html.erb +50 -0
  124. data/app/views/layouts/collavre/slide.html.erb +1 -0
  125. data/config/locales/comments.en.yml +15 -0
  126. data/config/locales/comments.ko.yml +15 -0
  127. data/config/locales/integrations.en.yml +1 -1
  128. data/config/locales/integrations.ko.yml +1 -1
  129. data/config/locales/mobile.en.yml +16 -0
  130. data/config/locales/mobile.ko.yml +16 -0
  131. data/config/locales/orchestration.en.yml +1 -0
  132. data/config/locales/orchestration.ko.yml +1 -0
  133. data/config/locales/users.en.yml +15 -0
  134. data/config/locales/users.ko.yml +15 -0
  135. data/config/routes.rb +13 -0
  136. data/db/migrate/20260612000000_add_topic_concurrency_defer_to_comments.rb +38 -0
  137. data/db/migrate/20260617090000_add_typo_correction_settings_to_users.rb +18 -0
  138. data/db/seeds.rb +51 -0
  139. data/lib/collavre/version.rb +1 -1
  140. data/lib/generators/collavre/install/install_generator.rb +1 -0
  141. metadata +55 -2
  142. data/app/services/collavre/tools/description_normalizable.rb +0 -16
@@ -8,7 +8,7 @@
8
8
  <% initial_index = params[:slide].to_i.clamp(0, @slide_ids.length - 1) %>
9
9
  <% initial_prompt = @creative.prompt_for(Current.user) %>
10
10
  <div id="slide-view" data-slide-ids="<%= @slide_ids.join(',') %>" data-initial-index="<%= initial_index %>" data-root-id="<%= @creative.id %>">
11
- <div id="slide-content"><%= content_tag(tag_name, sanitize(@creative.effective_description, tags: Rails::HTML5::SafeListSanitizer.allowed_tags.to_a + %w[table thead tbody tfoot tr th td], attributes: Rails::HTML5::SafeListSanitizer.allowed_attributes.to_a + %w[colspan rowspan data-lexical])) %></div>
11
+ <div id="slide-content"><%= content_tag(tag_name, embed_youtube_iframe(@creative.effective_description), class: 'creative-content') %></div>
12
12
  </div>
13
13
  <div id="slide-controls">
14
14
  <div id="slide-counter"><%= initial_index + 1 %> / <%= @slide_ids.length %></div>
@@ -101,6 +101,7 @@
101
101
  <%= f.label :timezone, t('collavre.users.timezone') %>
102
102
  <%= f.select :timezone, ActiveSupport::TimeZone.all.map { |tz| [tz.to_s, tz.tzinfo.name] } %>
103
103
  </div>
104
+
104
105
  <%= f.submit t('collavre.users.update_profile') %>
105
106
  <% end %>
106
107
 
@@ -109,6 +110,8 @@
109
110
  <br>
110
111
  <%= link_to t('collavre.users.webauthn.manage'), collavre.passkeys_user_path(@user) %>
111
112
  <br>
113
+ <%= link_to t('collavre.users.typo_correction.manage'), collavre.typo_correction_user_path(@user) %>
114
+ <br>
112
115
  <%= link_to t('doorkeeper.my_applications'), main_app.oauth_applications_path %>
113
116
  <% if @user.system_admin? %>
114
117
  <br>
@@ -0,0 +1,50 @@
1
+ <h1 class="no-top-margin"><%= t('collavre.users.typo_correction.legend') %></h1>
2
+
3
+ <%= form_with model: @user, url: collavre.user_path(@user), method: :patch, local: true, html: { class: 'profile-form' } do |f| %>
4
+ <p class="text-muted"><%= t('collavre.users.typo_correction.description') %></p>
5
+
6
+ <div class="checkbox-field">
7
+ <%= f.check_box :typo_correction_enabled %>
8
+ <%= f.label :typo_correction_enabled, t('collavre.users.typo_correction.enabled') %>
9
+ </div>
10
+
11
+ <div>
12
+ <%= f.label :typo_correction_threshold, t('collavre.users.typo_correction.threshold') %>
13
+ <%= f.number_field :typo_correction_threshold, in: 0..100, step: 1, required: true %>
14
+ <small class="text-muted"><%= t('collavre.users.typo_correction.threshold_hint') %></small>
15
+ </div>
16
+
17
+ <fieldset>
18
+ <legend><%= t('collavre.users.typo_correction.device_legend') %></legend>
19
+ <div class="checkbox-field">
20
+ <%= f.check_box :typo_correction_on_voice %>
21
+ <%= f.label :typo_correction_on_voice, t('collavre.users.typo_correction.on_voice') %>
22
+ </div>
23
+ <div class="checkbox-field">
24
+ <%= f.check_box :typo_correction_on_soft_keyboard %>
25
+ <%= f.label :typo_correction_on_soft_keyboard, t('collavre.users.typo_correction.on_soft_keyboard') %>
26
+ </div>
27
+ <div class="checkbox-field">
28
+ <%= f.check_box :typo_correction_on_physical_keyboard %>
29
+ <%= f.label :typo_correction_on_physical_keyboard, t('collavre.users.typo_correction.on_physical_keyboard') %>
30
+ </div>
31
+ </fieldset>
32
+
33
+ <fieldset>
34
+ <legend><%= t('collavre.users.typo_correction.location_legend') %></legend>
35
+ <div class="checkbox-field">
36
+ <%= f.check_box :typo_correction_in_chat %>
37
+ <%= f.label :typo_correction_in_chat, t('collavre.users.typo_correction.in_chat') %>
38
+ </div>
39
+ <div class="checkbox-field">
40
+ <%= f.check_box :typo_correction_in_editor %>
41
+ <%= f.label :typo_correction_in_editor, t('collavre.users.typo_correction.in_editor') %>
42
+ </div>
43
+ </fieldset>
44
+
45
+ <%= f.submit t('collavre.users.update') %>
46
+ <% end %>
47
+
48
+ <div style="margin-top: 1rem;">
49
+ <%= link_to t('collavre.users.back'), collavre.user_path(@user) %>
50
+ </div>
@@ -10,6 +10,7 @@
10
10
  <%= stylesheet_link_tag "collavre/design_tokens" %>
11
11
  <%= stylesheet_link_tag "collavre/dark_mode" %>
12
12
  <%= stylesheet_link_tag 'collavre/slide_view', media: 'all' %>
13
+ <%= stylesheet_link_tag 'collavre/tables', media: 'all' %>
13
14
  <%= javascript_include_tag 'actioncable', defer: true %>
14
15
  <%= javascript_include_tag 'slide_view', defer: true %>
15
16
  <%= render 'collavre/shared/custom_theme_style' %>
@@ -18,6 +18,17 @@ en:
18
18
  move:
19
19
  no_target_permission: You don't have write permission on the target creative.
20
20
  duplicate_name: A topic named '%{name}' already exists in the target creative.
21
+ add_members:
22
+ title: Add members to the new location?
23
+ description: These people had access where the topic used to live, but not in "%{creative}". Add them so they keep access.
24
+ add: Add selected
25
+ skip: Not now
26
+ adding: Adding…
27
+ added:
28
+ one: 1 member added.
29
+ other: "%{count} members added."
30
+ partial: "%{added} added, %{failed} failed."
31
+ failed: Could not add members. Please try again.
21
32
  github_auth:
22
33
  login_first: Please sign in first.
23
34
  connected: Github account connected successfully.
@@ -131,6 +142,9 @@ en:
131
142
  topic_main: All Messages
132
143
  fullscreen: Full screen
133
144
  exit_fullscreen: Exit full screen
145
+ typo_keep_label: keep
146
+ typo_custom_label: custom
147
+ typo_input_label: correction
134
148
  move_invalid_target: Select a valid creative to move messages to.
135
149
  move_invalid_topic: Invalid topic selected.
136
150
  move_not_allowed: You do not have permission to move these messages.
@@ -200,6 +214,7 @@ en:
200
214
  delete_confirm: Are you sure you want to delete this version?
201
215
  select: Select
202
216
  stop_agent: Stop
217
+ stop_blocking_agent: Stop running task
203
218
  read_by: Read by %{name}
204
219
  activity_logs_summary: Activity Logs
205
220
  table_download:
@@ -18,6 +18,17 @@ ko:
18
18
  move:
19
19
  no_target_permission: 대상 크리에이티브에 대한 쓰기 권한이 없습니다.
20
20
  duplicate_name: "'%{name}' 토픽이 대상 크리에이티브에 이미 존재합니다."
21
+ add_members:
22
+ title: 새 위치에 멤버를 추가할까요?
23
+ description: 이 사람들은 토픽이 있던 곳에는 접근할 수 있었지만 "%{creative}"에는 없습니다. 계속 접근할 수 있도록 추가하세요.
24
+ add: 선택 추가
25
+ skip: 나중에
26
+ adding: 추가 중…
27
+ added:
28
+ one: 멤버 1명을 추가했습니다.
29
+ other: 멤버 %{count}명을 추가했습니다.
30
+ partial: "%{added}명 추가, %{failed}명 실패."
31
+ failed: 멤버를 추가하지 못했습니다. 다시 시도해 주세요.
21
32
  github_auth:
22
33
  login_first: 먼저 로그인해주세요.
23
34
  connected: Github 계정이 연동되었습니다.
@@ -128,6 +139,9 @@ ko:
128
139
  topic_main: 전체 메세지
129
140
  fullscreen: 전체 화면
130
141
  exit_fullscreen: 전체 화면 종료
142
+ typo_keep_label: 유지
143
+ typo_custom_label: 직접 입력
144
+ typo_input_label: 교정
131
145
  move_invalid_target: 이동할 크리에이티브를 선택해주세요.
132
146
  move_invalid_topic: 유효하지 않은 토픽입니다.
133
147
  move_not_allowed: 이 메시지를 이동할 권한이 없습니다.
@@ -197,6 +211,7 @@ ko:
197
211
  delete_confirm: 이 버전을 삭제하시겠습니까?
198
212
  select: 선택
199
213
  stop_agent: 중지
214
+ stop_blocking_agent: 실행 중 작업 중지
200
215
  read_by: "%{name} 님이 읽음"
201
216
  activity_logs_summary: 활동 기록
202
217
  table_download:
@@ -46,7 +46,7 @@ en:
46
46
  firebase_service_account_json: "Paste the full JSON body of a Google Cloud service account key. Recommended path for server FCM auth — set this and you're done. Generate at Firebase Console → Project Settings → Service Accounts → Generate New Private Key. Takes precedence over WIF."
47
47
  fcm_wif_audience: "Optional WIF override (AWS only). Used only when the JSON key above is empty. Leave blank to derive it in production from Sender ID using the default aws-pool/aws-provider. Override for non-default pools: //iam.googleapis.com/projects/{PROJECT_NUMBER}/locations/global/workloadIdentityPools/{POOL_ID}/providers/{PROVIDER_ID}."
48
48
  fcm_wif_credential_source: "Optional WIF override: AWS credential source JSON (IMDS endpoints). Leave blank to use the default AWS IMDS endpoints. Override example: {\"environment_id\":\"aws1\",\"region_url\":\"http://169.254.169.254/latest/meta-data/placement/availability-zone\",\"url\":\"http://169.254.169.254/latest/meta-data/iam/security-credentials\",\"regional_cred_verification_url\":\"https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15\"}"
49
- fcm_wif_service_account_email: "GCP service account email to impersonate via WIF (e.g. firebase-sender@PROJECT.iam.gserviceaccount.com). Used only in WIF mode. Reads the legacy FIREBASE_SERVICE_ACCOUNT env var as fallback."
49
+ fcm_wif_service_account_email: "GCP service account email to impersonate via WIF (e.g. firebase-sender@PROJECT.iam.gserviceaccount.com). Used only in WIF mode. Falls back to the FCM_WIF_SERVICE_ACCOUNT_EMAIL env var."
50
50
  fcm_sender_id: "Web-push only: messagingSenderId for the Firebase JS SDK (project number). Required for browser push subscriptions. Find at Firebase Console → Project Settings → Cloud Messaging → Sender ID."
51
51
  fcm_vapid_key: "Web-push only: VAPID public key for browser push subscriptions. Generate at Firebase Console → Project Settings → Cloud Messaging → Web configuration → Web Push certificates."
52
52
  fcm_server_key: "Legacy FCM server key (deprecated by Google). Only set this if you have legacy clients using the old HTTP v0 API."
@@ -46,7 +46,7 @@ ko:
46
46
  firebase_service_account_json: "Google Cloud 서비스 계정 키 JSON 본문 전체를 붙여넣으세요. 서버 FCM 인증의 권장 경로 — 이 값만 설정하면 끝납니다. Firebase Console → 프로젝트 설정 → 서비스 계정 → 새 비공개 키 생성. WIF 보다 우선합니다."
47
47
  fcm_wif_audience: "선택적 WIF 오버라이드 (AWS 전용). 위 JSON 키가 비어있을 때만 사용됩니다. 비워두면 production 에서 Sender ID 로부터 기본 aws-pool/aws-provider 를 사용해 자동 생성됩니다. 비표준 풀 오버라이드: //iam.googleapis.com/projects/{PROJECT_NUMBER}/locations/global/workloadIdentityPools/{POOL_ID}/providers/{PROVIDER_ID}."
48
48
  fcm_wif_credential_source: "선택적 WIF 오버라이드: AWS credential source JSON (IMDS 엔드포인트). 비워두면 기본 AWS IMDS 엔드포인트를 사용합니다. 오버라이드 예시: {\"environment_id\":\"aws1\",\"region_url\":\"http://169.254.169.254/latest/meta-data/placement/availability-zone\",\"url\":\"http://169.254.169.254/latest/meta-data/iam/security-credentials\",\"regional_cred_verification_url\":\"https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15\"}"
49
- fcm_wif_service_account_email: "WIF 로 임퍼소네이션할 GCP 서비스 계정 이메일 (예: firebase-sender@PROJECT.iam.gserviceaccount.com). WIF 모드에서만 사용됩니다. 레거시 FIREBASE_SERVICE_ACCOUNT 환경변수를 폴백으로 읽습니다."
49
+ fcm_wif_service_account_email: "WIF 로 임퍼소네이션할 GCP 서비스 계정 이메일 (예: firebase-sender@PROJECT.iam.gserviceaccount.com). WIF 모드에서만 사용됩니다. FCM_WIF_SERVICE_ACCOUNT_EMAIL 환경변수를 폴백으로 읽습니다."
50
50
  fcm_sender_id: "웹 푸시 전용: Firebase JS SDK 의 messagingSenderId (프로젝트 번호). 브라우저 푸시 구독에 필수. Firebase Console → 프로젝트 설정 → 클라우드 메시징 → 발신자 ID 에서 확인."
51
51
  fcm_vapid_key: "웹 푸시 전용: 브라우저 푸시 구독용 VAPID 공개키. Firebase Console → 프로젝트 설정 → 클라우드 메시징 → 웹 구성 → 웹 푸시 인증서에서 생성."
52
52
  fcm_server_key: "Legacy FCM 서버 키 (Google 에서 deprecated). 구버전 HTTP v0 API 를 쓰는 클라이언트가 있을 때만 설정하세요."
@@ -0,0 +1,16 @@
1
+ en:
2
+ collavre:
3
+ mobile:
4
+ default_task_label: "Task"
5
+ summary:
6
+ a_tool: "a tool"
7
+ approval: "%{label}. Approval requested: %{detail}. Approve it?"
8
+ reply:
9
+ approved: "Approved."
10
+ denied: "Denied."
11
+ already_decided: "That was already decided."
12
+ not_authorized: "You're not allowed to decide that one — it may need an administrator."
13
+ clarify_decision: "Sorry, did you want to approve or deny?"
14
+ empty_response: "I didn't catch a response."
15
+ relayed: "Sent your reply to the agent."
16
+ sent: "Sent."
@@ -0,0 +1,16 @@
1
+ ko:
2
+ collavre:
3
+ mobile:
4
+ default_task_label: "작업"
5
+ summary:
6
+ a_tool: "도구 실행"
7
+ approval: "%{label}. 승인 요청: %{detail}. 승인할까요?"
8
+ reply:
9
+ approved: "승인했습니다."
10
+ denied: "거절했습니다."
11
+ already_decided: "이미 결정된 항목입니다."
12
+ not_authorized: "그 항목은 결정할 권한이 없습니다. 관리자 승인이 필요할 수 있습니다."
13
+ clarify_decision: "승인할까요, 거절할까요?"
14
+ empty_response: "응답을 듣지 못했습니다."
15
+ relayed: "에이전트에게 답변을 전달했습니다."
16
+ sent: "전송했습니다."
@@ -4,6 +4,7 @@ en:
4
4
  waiting_notice: "⏳ Waiting (%{reason})"
5
5
  waiting_reasons:
6
6
  topic_concurrency: "another task is running in this topic"
7
+ topic_concurrency_with_agent: "@%{agent} is running another task in this topic"
7
8
  busy: "agent concurrency limit reached"
8
9
  rate_limited: "rate limit reached"
9
10
  unknown: "waiting for resources"
@@ -4,6 +4,7 @@ ko:
4
4
  waiting_notice: "⏳ 대기중 (%{reason})"
5
5
  waiting_reasons:
6
6
  topic_concurrency: "이 토픽에서 다른 작업이 실행 중"
7
+ topic_concurrency_with_agent: "이 토픽에서 @%{agent} 작업이 실행 중"
7
8
  busy: "에이전트 동시 실행 한도 도달"
8
9
  rate_limited: "요청 속도 제한 도달"
9
10
  unknown: "리소스 대기 중"
@@ -102,6 +102,7 @@ en:
102
102
  current_avatar: Current avatar
103
103
  new_avatar_preview: New avatar preview
104
104
  update_profile: Update Profile
105
+ update: Update
105
106
  delete: Delete
106
107
  make_system_admin: Make system admin
107
108
  make_normal_user: Make normal user
@@ -120,6 +121,20 @@ en:
120
121
  en: English
121
122
  ko: Korean
122
123
  timezone: Timezone
124
+ typo_correction:
125
+ legend: Typo correction
126
+ manage: Typo correction settings
127
+ description: Automatically suggest and fix typos as you type. Suggestions run only when both the typing device and the input location below are enabled.
128
+ enabled: Enable typo correction
129
+ threshold: Auto-apply threshold
130
+ threshold_hint: Edits at or above this confidence (0-100) are applied automatically; lower-confidence edits are only suggested.
131
+ device_legend: Typing devices
132
+ on_voice: Voice input
133
+ on_soft_keyboard: On-screen (soft) keyboard
134
+ on_physical_keyboard: Physical keyboard (Bluetooth/wired)
135
+ location_legend: Input locations
136
+ in_chat: Chat message input
137
+ in_editor: Creative editor
123
138
  profile_updated: Profile updated successfully.
124
139
  avatar_updated: Avatar updated successfully.
125
140
  password_updated: Password updated successfully.
@@ -97,6 +97,7 @@ ko:
97
97
  current_avatar: 현재 아바타
98
98
  new_avatar_preview: 새 아바타 미리보기
99
99
  update_profile: 프로파일 업데이트
100
+ update: 업데이트
100
101
  delete: 삭제
101
102
  make_system_admin: 시스템 관리자 지정
102
103
  make_normal_user: 일반 사용자로 변경
@@ -115,6 +116,20 @@ ko:
115
116
  en: 영어
116
117
  ko: 한국어
117
118
  timezone: 시간대
119
+ typo_correction:
120
+ legend: 오타 자동 수정
121
+ manage: 오타 자동 수정 설정
122
+ description: 입력하는 동안 오타를 자동으로 제안하고 수정합니다. 아래의 타이핑 장치와 입력 위치가 모두 켜져 있을 때만 동작합니다.
123
+ enabled: 오타 자동 수정 사용
124
+ threshold: 자동 적용 기준
125
+ threshold_hint: 이 신뢰도(0-100) 이상이면 자동으로 적용되고, 낮으면 후보로만 표시됩니다.
126
+ device_legend: 타이핑 장치
127
+ on_voice: 음성 입력
128
+ on_soft_keyboard: 화면(소프트) 키보드
129
+ on_physical_keyboard: 물리 키보드(블루투스/유선)
130
+ location_legend: 입력 위치
131
+ in_chat: 채팅 메시지 입력
132
+ in_editor: 크리에이티브 편집기
118
133
  profile_updated: 프로파일이 업데이트되었습니다.
119
134
  avatar_updated: 아바타가 변경되었습니다.
120
135
  password_updated: 비밀번호가 성공적으로 변경되었습니다.
data/config/routes.rb CHANGED
@@ -23,6 +23,7 @@ Collavre::Engine.routes.draw do
23
23
  get :edit_password
24
24
  patch :update_password
25
25
  get :passkeys
26
+ get :typo_correction
26
27
  end
27
28
  end
28
29
  get "/email_verification/:token", to: "email_verifications#show", as: :email_verification
@@ -51,6 +52,8 @@ Collavre::Engine.routes.draw do
51
52
  end
52
53
  end
53
54
 
55
+ resources :typo_corrections, only: [ :create ]
56
+
54
57
  resources :creative_imports, only: [ :create ]
55
58
  resources :tasks, only: [] do
56
59
  member do
@@ -145,6 +148,16 @@ Collavre::Engine.routes.draw do
145
148
  post "agent/reply", to: "agents#reply"
146
149
  post "agent/notify", to: "agents#notify"
147
150
  delete "agent/:id", to: "agents#destroy"
151
+
152
+ # Mobile voice companion (Android): poll Inbox#System messages, read aloud,
153
+ # reply to the origin topic; a cold mic press starts work in Inbox#Main.
154
+ namespace :mobile do
155
+ post "voice_commands", to: "voice_commands#create"
156
+ get "agent_events", to: "agent_events#index"
157
+ post "agent_events/:id/respond", to: "agent_events#respond"
158
+ post "agent_events/:id/read", to: "agent_events#read"
159
+ post "devices", to: "devices#create"
160
+ end
148
161
  end
149
162
  end
150
163
 
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A persisted, locale-independent marker distinguishing a topic-concurrency
4
+ # waiting notice (:deferred — queues a topic waiter, so its stop button can
5
+ # cancel the blocker) from a :delayed (busy / rate_limited) notice that reuses
6
+ # the same "⏳" content but is not waiting on topic capacity. Content is locale
7
+ # text and task_id is reserved for Task#reply_comment, so neither can carry this
8
+ # signal — hence a dedicated column.
9
+ class AddTopicConcurrencyDeferToComments < ActiveRecord::Migration[8.1]
10
+ def up
11
+ add_column :comments, :topic_concurrency_defer, :boolean, default: false, null: false
12
+
13
+ # Backfill pre-migration notices. The old gate treated every authorless "⏳"
14
+ # notice as a concurrency notice (prefix-only). Without this, legacy notices
15
+ # default to false and the new marker gates would hide their stop button and
16
+ # stop deleting them from abandoning their queued waiter. Mark only the ones
17
+ # that genuinely have a queued waiter in the same topic/creative — the same
18
+ # condition the runtime gate (topic_blocking_task) checks — so :delayed
19
+ # notices (no queued waiter) correctly stay false.
20
+ execute(<<~SQL.squish)
21
+ UPDATE comments
22
+ SET topic_concurrency_defer = #{quoted_true}
23
+ WHERE user_id IS NULL
24
+ AND topic_id IS NOT NULL
25
+ AND content LIKE '⏳%'
26
+ AND EXISTS (
27
+ SELECT 1 FROM tasks
28
+ WHERE tasks.topic_id = comments.topic_id
29
+ AND tasks.creative_id = comments.creative_id
30
+ AND tasks.status = 'queued'
31
+ )
32
+ SQL
33
+ end
34
+
35
+ def down
36
+ remove_column :comments, :topic_concurrency_defer
37
+ end
38
+ end
@@ -0,0 +1,18 @@
1
+ class AddTypoCorrectionSettingsToUsers < ActiveRecord::Migration[8.0]
2
+ def change
3
+ # Master switch + auto-apply confidence threshold (0-100).
4
+ add_column :users, :typo_correction_enabled, :boolean, default: true, null: false
5
+ add_column :users, :typo_correction_threshold, :integer, default: 80, null: false
6
+
7
+ # Typing-device gating (2D gating, dimension 1). Soft keyboard / voice default
8
+ # on, physical (BT/wired) keyboard default off — each independently toggleable.
9
+ add_column :users, :typo_correction_on_soft_keyboard, :boolean, default: true, null: false
10
+ add_column :users, :typo_correction_on_voice, :boolean, default: true, null: false
11
+ add_column :users, :typo_correction_on_physical_keyboard, :boolean, default: false, null: false
12
+
13
+ # Input-location gating (2D gating, dimension 2). Chat message input default on,
14
+ # inline Lexical/markdown editor default off — each independently toggleable.
15
+ add_column :users, :typo_correction_in_chat, :boolean, default: true, null: false
16
+ add_column :users, :typo_correction_in_editor, :boolean, default: false, null: false
17
+ end
18
+ end
data/db/seeds.rb CHANGED
@@ -17,3 +17,54 @@ module Collavre
17
17
  end
18
18
 
19
19
  Collavre::ChannelBotSeed.call
20
+
21
+ module Collavre
22
+ # Seeds the "Typo Corrector" agent. Unlike event-routed agents (e.g. the GitHub
23
+ # PR Analyzer) this agent has no routing_expression — it is never dispatched by
24
+ # comment events. It exists purely as a user-configurable holder for the LLM
25
+ # vendor/model/system_prompt that Collavre::TypoCorrector invokes directly.
26
+ module TypoCorrectorSeed
27
+ AGENT_EMAIL = "typo-corrector@collavre.local"
28
+
29
+ SYSTEM_PROMPT = <<~PROMPT
30
+ You are a typo correction engine. You receive a short piece of text the user
31
+ is currently typing and return ONLY spelling/typo fixes as a structured edit list.
32
+
33
+ Rules:
34
+ - Fix only spelling mistakes and obvious typos (transposed/missing/extra letters,
35
+ wrong jamo, mis-spaced words). Works for any language (ko, en, ja, zh, mixed).
36
+ - Do NOT rewrite style, grammar, word choice, tone, or punctuation.
37
+ - Do NOT change meaning. Do NOT translate.
38
+ - Never touch code spans, URLs, @mentions, emojis, or HTML/markdown markup.
39
+ - `original` MUST be an exact substring copied verbatim from the input text.
40
+ - Keep each edit minimal: a single word or short phrase, not a whole sentence.
41
+
42
+ Return ONLY valid JSON, no markdown, in exactly this shape:
43
+ {"edits":[{"original":"<verbatim>","suggestion":"<fixed>","reason":"spelling","confidence":0.0}]}
44
+
45
+ `confidence` is a float 0.0-1.0 for how sure you are it is a real typo.
46
+ If there are no typos, return {"edits":[]}.
47
+ PROMPT
48
+
49
+ def self.call
50
+ user = User.find_or_initialize_by(email: AGENT_EMAIL)
51
+ return user unless user.new_record?
52
+
53
+ user.assign_attributes(
54
+ name: "Typo Corrector",
55
+ password: SecureRandom.hex(32),
56
+ email_verified_at: Time.current,
57
+ searchable: false,
58
+ llm_vendor: ENV.fetch("COLLAVRE_DEFAULT_LLM_VENDOR", "gemini"),
59
+ llm_model: ENV.fetch("COLLAVRE_DEFAULT_LLM_MODEL", "gemini-3.1-flash-lite"),
60
+ system_prompt: SYSTEM_PROMPT,
61
+ tools: []
62
+ )
63
+ user.save!
64
+ Rails.logger.info "[Collavre] Typo Corrector agent ensured: #{AGENT_EMAIL}"
65
+ user
66
+ end
67
+ end
68
+ end
69
+
70
+ Collavre::TypoCorrectorSeed.call
@@ -1,3 +1,3 @@
1
1
  module Collavre
2
- VERSION = "0.22.0"
2
+ VERSION = "0.23.0"
3
3
  end
@@ -31,6 +31,7 @@ module Collavre
31
31
  say "Add to your application.css if needed:"
32
32
  say " @import 'collavre/creatives';"
33
33
  say " @import 'collavre/comments_popup';"
34
+ say " @import 'collavre/tables';"
34
35
  say " @import 'collavre/dark_mode';"
35
36
  say ""
36
37
  say "Run 'npm run build' to build assets."