collavre 0.8.2 → 0.9.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.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/collavre/activity_logs.css +1 -1
- data/app/assets/stylesheets/collavre/comment_versions.css +10 -6
- data/app/assets/stylesheets/collavre/comments_popup.css +60 -2
- data/app/assets/stylesheets/collavre/creatives.css +7 -4
- data/app/assets/stylesheets/collavre/dark_mode.css +25 -5
- data/app/controllers/collavre/comments_controller.rb +2 -1
- data/app/controllers/collavre/topics_controller.rb +80 -3
- data/app/controllers/concerns/collavre/comments/approval_actions.rb +5 -0
- data/app/controllers/concerns/collavre/comments/batch_operations.rb +32 -0
- data/app/javascript/controllers/comment_version_controller.js +5 -1
- data/app/javascript/controllers/comments/contexts_controller.js +118 -3
- data/app/javascript/controllers/comments/list_controller.js +67 -1
- data/app/javascript/controllers/comments/popup_controller.js +143 -10
- data/app/javascript/controllers/comments/presence_controller.js +22 -0
- data/app/javascript/controllers/comments/topics_controller.js +105 -6
- data/app/javascript/controllers/creatives/expansion_controller.js +8 -0
- data/app/javascript/controllers/creatives/select_mode_controller.js +4 -0
- data/app/javascript/creatives/drag_drop/event_handlers.js +5 -5
- data/app/javascript/modules/creative_row_editor.js +13 -0
- data/app/jobs/collavre/compress_job.rb +24 -25
- data/app/jobs/collavre/merge_comments_job.rb +79 -0
- data/app/models/collavre/topic.rb +29 -0
- data/app/models/concerns/collavre/ai_agent_resolvable.rb +42 -0
- data/app/services/collavre/comments/topic_command.rb +27 -13
- data/app/views/collavre/comments/_comments_popup.html.erb +5 -1
- data/config/locales/comments.en.yml +11 -1
- data/config/locales/comments.ko.yml +11 -1
- data/config/routes.rb +2 -0
- data/lib/collavre/version.rb +1 -1
- metadata +3 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module Collavre
|
|
2
2
|
class CompressJob < ApplicationJob
|
|
3
|
+
include AiAgentResolvable
|
|
4
|
+
|
|
3
5
|
queue_as :default
|
|
4
6
|
|
|
5
7
|
def perform(creative_id, topic_id, user_id, extra_prompt = nil)
|
|
@@ -31,23 +33,37 @@ module Collavre
|
|
|
31
33
|
system_prompt += "\n\nAdditional instruction from the user: #{extra_prompt}"
|
|
32
34
|
end
|
|
33
35
|
|
|
34
|
-
# Find an AI agent on this creative
|
|
35
|
-
agent =
|
|
36
|
+
# Find an AI agent on this creative (no fallback — agent is required)
|
|
37
|
+
agent = resolve_ai_agent(creative, topic_id)
|
|
38
|
+
|
|
39
|
+
unless agent
|
|
40
|
+
error_msg = I18n.t("collavre.comments.compress_command.no_agent")
|
|
41
|
+
creative.comments.create!(user: user, topic_id: topic_id, content: "⚠️ #{error_msg}")
|
|
42
|
+
Rails.logger.error("[CompressJob] No AI agent found for creative #{creative_id}, topic #{topic_id}")
|
|
43
|
+
return
|
|
44
|
+
end
|
|
36
45
|
|
|
37
46
|
client = AiClient.new(
|
|
38
|
-
vendor: agent
|
|
39
|
-
model: agent
|
|
47
|
+
vendor: agent.llm_vendor,
|
|
48
|
+
model: agent.llm_model,
|
|
40
49
|
system_prompt: system_prompt,
|
|
41
|
-
llm_api_key: agent
|
|
50
|
+
llm_api_key: agent.llm_api_key || agent.creator&.llm_api_key,
|
|
51
|
+
context: {
|
|
52
|
+
creative: creative,
|
|
53
|
+
user: agent,
|
|
54
|
+
topic_id: topic_id
|
|
55
|
+
}
|
|
42
56
|
)
|
|
43
57
|
|
|
44
58
|
summary = String.new
|
|
45
|
-
client.chat([ { role: "user", text: conversation } ]) do |delta|
|
|
59
|
+
result = client.chat([ { role: "user", text: conversation } ]) do |delta|
|
|
46
60
|
summary << delta
|
|
47
61
|
end
|
|
48
62
|
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
# AiClient returns nil on error (but still yields error text as delta).
|
|
64
|
+
# Check both: return value must be truthy AND content must be non-blank.
|
|
65
|
+
if result.nil? || summary.blank?
|
|
66
|
+
Rails.logger.error("[CompressJob] AI failed for topic #{topic_id}")
|
|
51
67
|
return
|
|
52
68
|
end
|
|
53
69
|
|
|
@@ -71,22 +87,5 @@ module Collavre
|
|
|
71
87
|
rescue ActiveRecord::RecordNotFound => e
|
|
72
88
|
Rails.logger.error("[CompressJob] Record not found: #{e.message}")
|
|
73
89
|
end
|
|
74
|
-
|
|
75
|
-
private
|
|
76
|
-
|
|
77
|
-
def find_ai_agent(creative)
|
|
78
|
-
# Look for an AI agent with access to this creative
|
|
79
|
-
creative.effective_origin.all_shared_users(:feedback)
|
|
80
|
-
.map(&:user)
|
|
81
|
-
.find(&:ai_user?)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def default_vendor
|
|
85
|
-
"google"
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def default_model
|
|
89
|
-
"gemini-3-flash-preview"
|
|
90
|
-
end
|
|
91
90
|
end
|
|
92
91
|
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module Collavre
|
|
2
|
+
class MergeCommentsJob < ApplicationJob
|
|
3
|
+
include AiAgentResolvable
|
|
4
|
+
|
|
5
|
+
queue_as :default
|
|
6
|
+
|
|
7
|
+
SYSTEM_PROMPT = <<~PROMPT.freeze
|
|
8
|
+
You are merging multiple chat messages into a single coherent message.
|
|
9
|
+
Synthesize the content from all messages, preserving all important information,
|
|
10
|
+
decisions, action items, and context.
|
|
11
|
+
Do not add commentary about the merge process itself.
|
|
12
|
+
Respond in the same language as the original messages.
|
|
13
|
+
Use markdown formatting for readability.
|
|
14
|
+
PROMPT
|
|
15
|
+
|
|
16
|
+
def perform(creative_id, comment_ids, user_id) # rubocop:disable Lint/UnusedMethodArgument -- user_id reserved for future audit/notification use
|
|
17
|
+
creative = Creative.find(creative_id)
|
|
18
|
+
|
|
19
|
+
# Fetch comments in chronological order
|
|
20
|
+
comments = creative.comments
|
|
21
|
+
.where(id: comment_ids)
|
|
22
|
+
.order(created_at: :asc)
|
|
23
|
+
.includes(:user)
|
|
24
|
+
.to_a
|
|
25
|
+
|
|
26
|
+
return if comments.size < 2
|
|
27
|
+
|
|
28
|
+
target_comment = comments.first
|
|
29
|
+
topic_id = target_comment.topic_id
|
|
30
|
+
|
|
31
|
+
# Build conversation text
|
|
32
|
+
conversation = comments.map do |c|
|
|
33
|
+
author = c.user&.name || I18n.t("collavre.comments.anonymous")
|
|
34
|
+
"#{author}: #{c.content}"
|
|
35
|
+
end.join("\n\n")
|
|
36
|
+
|
|
37
|
+
# Resolve AI agent (same as /compress — agent is required)
|
|
38
|
+
agent = resolve_ai_agent(creative, topic_id)
|
|
39
|
+
|
|
40
|
+
unless agent
|
|
41
|
+
Rails.logger.error("[MergeCommentsJob] No AI agent found for creative #{creative_id}")
|
|
42
|
+
return
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
client = AiClient.new(
|
|
46
|
+
vendor: agent.llm_vendor,
|
|
47
|
+
model: agent.llm_model,
|
|
48
|
+
system_prompt: SYSTEM_PROMPT,
|
|
49
|
+
llm_api_key: agent.llm_api_key || agent.creator&.llm_api_key,
|
|
50
|
+
context: {
|
|
51
|
+
creative: creative,
|
|
52
|
+
user: agent,
|
|
53
|
+
topic_id: topic_id
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
merged_content = String.new
|
|
58
|
+
result = client.chat([ { role: "user", text: conversation } ]) do |delta|
|
|
59
|
+
merged_content << delta
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# AiClient returns nil on error (but still yields error text as delta).
|
|
63
|
+
# Check both: return value must be truthy AND content must be non-blank.
|
|
64
|
+
if result.nil? || merged_content.blank?
|
|
65
|
+
Rails.logger.error("[MergeCommentsJob] AI failed for comments #{comment_ids}")
|
|
66
|
+
return
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Update the first comment and delete the rest atomically
|
|
70
|
+
remaining_ids = comments[1..].map(&:id)
|
|
71
|
+
ActiveRecord::Base.transaction do
|
|
72
|
+
target_comment.update!(content: merged_content)
|
|
73
|
+
creative.comments.where(id: remaining_ids).destroy_all
|
|
74
|
+
end
|
|
75
|
+
rescue ActiveRecord::RecordNotFound => e
|
|
76
|
+
Rails.logger.error("[MergeCommentsJob] Record not found: #{e.message}")
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -17,6 +17,35 @@ module Collavre
|
|
|
17
17
|
|
|
18
18
|
default_scope { order(:position) }
|
|
19
19
|
|
|
20
|
+
# Returns the primary agent User for this topic (from orchestration policy)
|
|
21
|
+
def primary_agent
|
|
22
|
+
policy = OrchestratorPolicy.find_by(
|
|
23
|
+
policy_type: "arbitration",
|
|
24
|
+
scope_type: "Topic",
|
|
25
|
+
scope_id: id
|
|
26
|
+
)
|
|
27
|
+
return nil unless policy&.config&.dig("primary_agent_id")
|
|
28
|
+
|
|
29
|
+
User.find_by(id: policy.config["primary_agent_id"])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Sets or replaces the primary agent for this topic
|
|
33
|
+
def set_primary_agent!(agent)
|
|
34
|
+
policy = OrchestratorPolicy.find_or_initialize_by(
|
|
35
|
+
policy_type: "arbitration",
|
|
36
|
+
scope_type: "Topic",
|
|
37
|
+
scope_id: id
|
|
38
|
+
)
|
|
39
|
+
policy.update!(
|
|
40
|
+
config: {
|
|
41
|
+
"strategy" => "primary_first",
|
|
42
|
+
"primary_agent_id" => agent.id
|
|
43
|
+
},
|
|
44
|
+
priority: 10,
|
|
45
|
+
enabled: true
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
20
49
|
def archived?
|
|
21
50
|
archived_at.present?
|
|
22
51
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Collavre
|
|
2
|
+
module AiAgentResolvable
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
# Resolve AI agent using orchestration rules:
|
|
8
|
+
# 1. Topic's primary agent (from OrchestratorPolicy)
|
|
9
|
+
# 2. Fallback: any AI agent with feedback permission on the creative
|
|
10
|
+
def resolve_ai_agent(creative, topic_id)
|
|
11
|
+
if topic_id.present?
|
|
12
|
+
context = build_ai_agent_policy_context(creative, topic_id)
|
|
13
|
+
resolver = Orchestration::PolicyResolver.new(context)
|
|
14
|
+
primary_id = resolver.primary_agent_id
|
|
15
|
+
|
|
16
|
+
if primary_id.present?
|
|
17
|
+
agent = User.find_by(id: primary_id)
|
|
18
|
+
return agent if agent&.ai_user?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
creative.effective_origin.all_shared_users(:feedback)
|
|
23
|
+
.map(&:user)
|
|
24
|
+
.find(&:ai_user?)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def build_ai_agent_policy_context(creative, topic_id)
|
|
28
|
+
context = {}
|
|
29
|
+
context["creative"] = { "id" => creative.id }
|
|
30
|
+
context["topic"] = { "id" => topic_id } if topic_id.present?
|
|
31
|
+
context
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def default_vendor
|
|
35
|
+
"google"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def default_model
|
|
39
|
+
"gemini-3-flash-preview"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -72,29 +72,43 @@ module Collavre
|
|
|
72
72
|
|
|
73
73
|
if primary_agent
|
|
74
74
|
set_primary_agent(topic, primary_agent)
|
|
75
|
+
broadcast_topic_created(topic, primary_agent)
|
|
75
76
|
I18n.t("collavre.comments.topic_command.created_with_agent",
|
|
76
77
|
name: topic.name,
|
|
77
78
|
agent: primary_agent.name)
|
|
78
79
|
else
|
|
80
|
+
broadcast_topic_created(topic)
|
|
79
81
|
I18n.t("collavre.comments.topic_command.created", name: topic.name)
|
|
80
82
|
end
|
|
81
83
|
end
|
|
82
84
|
end
|
|
83
85
|
|
|
86
|
+
def broadcast_topic_created(topic, agent = nil)
|
|
87
|
+
data = { action: "created", topic: topic.slice(:id, :name), user_id: user.id }
|
|
88
|
+
if agent
|
|
89
|
+
data[:topic][:primary_agent] = {
|
|
90
|
+
id: agent.id,
|
|
91
|
+
name: agent.display_name,
|
|
92
|
+
avatar_url: resolve_avatar_url(agent)
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
TopicsChannel.broadcast_to(creative, data)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def resolve_avatar_url(agent)
|
|
99
|
+
if agent.avatar.attached?
|
|
100
|
+
Rails.application.routes.url_helpers.rails_blob_url(
|
|
101
|
+
agent.avatar, only_path: true
|
|
102
|
+
)
|
|
103
|
+
elsif agent.avatar_url.present?
|
|
104
|
+
agent.avatar_url
|
|
105
|
+
else
|
|
106
|
+
ActionController::Base.helpers.asset_path("default_avatar.svg")
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
84
110
|
def set_primary_agent(topic, agent)
|
|
85
|
-
|
|
86
|
-
policy_type: "arbitration",
|
|
87
|
-
scope_type: "Topic",
|
|
88
|
-
scope_id: topic.id
|
|
89
|
-
)
|
|
90
|
-
policy.update!(
|
|
91
|
-
config: {
|
|
92
|
-
"strategy" => "primary_first",
|
|
93
|
-
"primary_agent_id" => agent.id
|
|
94
|
-
},
|
|
95
|
-
priority: 10,
|
|
96
|
-
enabled: true
|
|
97
|
-
)
|
|
111
|
+
topic.set_primary_agent!(agent)
|
|
98
112
|
end
|
|
99
113
|
end
|
|
100
114
|
end
|
|
@@ -29,12 +29,15 @@
|
|
|
29
29
|
data-voice-stop-text="<%= t('collavre.comments.voice_stop') %>"
|
|
30
30
|
data-move-no-selection-text="<%= t('collavre.comments.move_no_selection') %>"
|
|
31
31
|
data-move-error-text="<%= t('collavre.comments.move_error') %>"
|
|
32
|
+
data-selection-select-all-text="<%= t('collavre.comments.select_all') %>"
|
|
32
33
|
data-selection-count-text="<%= t('collavre.comments.selection_count') %>"
|
|
33
34
|
data-selection-delete-text="<%= t('collavre.comments.selection_delete') %>"
|
|
34
35
|
data-selection-move-text="<%= t('collavre.comments.selection_move') %>"
|
|
35
36
|
data-selection-topic-move-text="<%= t('collavre.comments.selection_topic_move') %>"
|
|
36
37
|
data-selection-close-text="<%= t('collavre.comments.selection_close') %>"
|
|
37
38
|
data-selection-drag-hint-text="<%= t('collavre.comments.selection_drag_hint') %>"
|
|
39
|
+
data-selection-merge-text="<%= t('collavre.comments.selection_merge') %>"
|
|
40
|
+
data-merge-confirm-text="<%= t('collavre.comments.merge_confirm') %>"
|
|
38
41
|
data-batch-delete-confirm-text="<%= t('collavre.comments.batch_delete_confirm') %>"
|
|
39
42
|
data-topic-search-placeholder-text="<%= t('collavre.comments.topic_search_placeholder') %>"
|
|
40
43
|
data-topic-main-text="<%= t('collavre.comments.topic_main') %>"
|
|
@@ -79,7 +82,8 @@
|
|
|
79
82
|
<div id="comment-contexts" data-comments--contexts-target="list" class="comment-contexts-list" style="display:none;"
|
|
80
83
|
data-inherited-label="<%= t('collavre.contexts.inherited_label', default: 'Inherited from parent') %>"
|
|
81
84
|
data-self-context-label="<%= t('collavre.contexts.self_context_label', default: 'Current creative context') %>"
|
|
82
|
-
data-navigate-label="<%= t('collavre.contexts.navigate_label', default: 'Go to creative') %>"
|
|
85
|
+
data-navigate-label="<%= t('collavre.contexts.navigate_label', default: 'Go to creative') %>"
|
|
86
|
+
data-action="dragover->comments--contexts#handleExternalDragOver drop->comments--contexts#handleExternalDrop dragleave->comments--contexts#handleExternalDragLeave"></div>
|
|
83
87
|
<div data-share-modal-target="container"></div>
|
|
84
88
|
<div id="comment-participants" data-comments--presence-target="participants" data-comments--mention-menu-target="participants"></div>
|
|
85
89
|
<div id="comment-topics" data-comments--topics-target="list" class="comment-topics-list"
|
|
@@ -8,6 +8,7 @@ en:
|
|
|
8
8
|
archive: Archive
|
|
9
9
|
unarchive: Restore
|
|
10
10
|
archived_topics: "Archived topics (%{count})"
|
|
11
|
+
not_ai_agent: Only AI agents can be set as primary agent.
|
|
11
12
|
move:
|
|
12
13
|
no_target_permission: You don't have write permission on the target creative.
|
|
13
14
|
duplicate_name: A topic named '%{name}' already exists in the target creative.
|
|
@@ -85,12 +86,20 @@ en:
|
|
|
85
86
|
move_no_selection: Select at least one message to move.
|
|
86
87
|
move_error: Unable to move messages.
|
|
87
88
|
add_participant: Add user
|
|
88
|
-
|
|
89
|
+
select_all: All
|
|
90
|
+
selection_count: "{count}/{total} selected"
|
|
89
91
|
selection_delete: Delete
|
|
90
92
|
selection_move: Move
|
|
91
93
|
selection_topic_move: Move to topic
|
|
92
94
|
selection_close: Cancel
|
|
93
95
|
selection_drag_hint: "You can also drag & drop to move to a topic"
|
|
96
|
+
selection_merge: Merge
|
|
97
|
+
merge_confirm: "Merge the selected messages into one? The first message will be updated and the rest will be deleted."
|
|
98
|
+
merge:
|
|
99
|
+
started: "⏳ Merging messages..."
|
|
100
|
+
minimum_required: "Select at least 2 messages to merge."
|
|
101
|
+
not_authorized: "You don't have permission to merge messages."
|
|
102
|
+
own_messages_only: "You can only merge your own messages or messages from your AI agents."
|
|
94
103
|
batch_delete_confirm: Are you sure you want to delete the selected messages?
|
|
95
104
|
batch_delete_no_selection: Select at least one message to delete.
|
|
96
105
|
batch_delete_not_found: Some selected messages could not be found.
|
|
@@ -124,6 +133,7 @@ en:
|
|
|
124
133
|
not_authorized: "You need write permission to compress a topic."
|
|
125
134
|
topic_required: "The /compress command can only be used within a topic."
|
|
126
135
|
nothing_to_compress: "No messages to compress in this topic."
|
|
136
|
+
no_agent: "No AI agent is available for this topic. Please assign an AI agent first."
|
|
127
137
|
started: "⏳ Compressing topic messages..."
|
|
128
138
|
summary_title: "Topic Summary — %{topic}"
|
|
129
139
|
failed: "Compress failed"
|
|
@@ -8,6 +8,7 @@ ko:
|
|
|
8
8
|
archive: 아카이브
|
|
9
9
|
unarchive: 복원
|
|
10
10
|
archived_topics: "아카이브된 토픽 (%{count}개)"
|
|
11
|
+
not_ai_agent: AI 에이전트만 Primary Agent로 설정할 수 있습니다.
|
|
11
12
|
move:
|
|
12
13
|
no_target_permission: 대상 크리에이티브에 대한 쓰기 권한이 없습니다.
|
|
13
14
|
duplicate_name: "'%{name}' 토픽이 대상 크리에이티브에 이미 존재합니다."
|
|
@@ -82,12 +83,20 @@ ko:
|
|
|
82
83
|
move_no_selection: 이동할 메시지를 선택해주세요.
|
|
83
84
|
move_error: 메시지를 이동할 수 없습니다.
|
|
84
85
|
add_participant: 사용자 추가
|
|
85
|
-
|
|
86
|
+
select_all: 전체
|
|
87
|
+
selection_count: "{count}/{total}개 선택"
|
|
86
88
|
selection_delete: 삭제
|
|
87
89
|
selection_move: 이동
|
|
88
90
|
selection_topic_move: 토픽 이동
|
|
89
91
|
selection_close: 취소
|
|
90
92
|
selection_drag_hint: "드래그&드롭으로도 토픽 이동 가능"
|
|
93
|
+
selection_merge: 병합
|
|
94
|
+
merge_confirm: "선택한 메시지를 하나로 병합하시겠습니까? 첫 번째 메시지가 업데이트되고 나머지는 삭제됩니다."
|
|
95
|
+
merge:
|
|
96
|
+
started: "⏳ 메시지 병합 중..."
|
|
97
|
+
minimum_required: "병합하려면 2개 이상의 메시지를 선택하세요."
|
|
98
|
+
not_authorized: "메시지를 병합할 권한이 없습니다."
|
|
99
|
+
own_messages_only: "자신의 메시지 또는 자신의 AI 에이전트 메시지만 병합할 수 있습니다."
|
|
91
100
|
batch_delete_confirm: 선택한 메시지를 삭제하시겠습니까?
|
|
92
101
|
batch_delete_no_selection: 삭제할 메시지를 선택해주세요.
|
|
93
102
|
batch_delete_not_found: 일부 선택한 메시지를 찾을 수 없습니다.
|
|
@@ -121,6 +130,7 @@ ko:
|
|
|
121
130
|
not_authorized: "토픽을 요약하려면 쓰기 권한이 필요합니다."
|
|
122
131
|
topic_required: "/compress 명령은 토픽 내에서만 사용할 수 있습니다."
|
|
123
132
|
nothing_to_compress: "이 토픽에 요약할 메세지가 없습니다."
|
|
133
|
+
no_agent: "이 토픽에 사용할 수 있는 AI 에이전트가 없습니다. 먼저 AI 에이전트를 지정해 주세요."
|
|
124
134
|
started: "⏳ 토픽 메세지를 요약하는 중..."
|
|
125
135
|
summary_title: "토픽 요약 — %{topic}"
|
|
126
136
|
failed: "요약 실패"
|
data/config/routes.rb
CHANGED
|
@@ -59,6 +59,7 @@ Collavre::Engine.routes.draw do
|
|
|
59
59
|
patch :move
|
|
60
60
|
patch :archive
|
|
61
61
|
patch :unarchive
|
|
62
|
+
patch :set_primary_agent
|
|
62
63
|
end
|
|
63
64
|
end
|
|
64
65
|
resources :comments, only: [ :index, :create, :destroy, :show, :update ] do
|
|
@@ -82,6 +83,7 @@ Collavre::Engine.routes.draw do
|
|
|
82
83
|
get :fullscreen
|
|
83
84
|
post :move
|
|
84
85
|
delete :batch_destroy
|
|
86
|
+
post :merge
|
|
85
87
|
get :commands
|
|
86
88
|
end
|
|
87
89
|
end
|
data/lib/collavre/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: collavre
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Collavre
|
|
@@ -324,6 +324,7 @@ files:
|
|
|
324
324
|
- app/jobs/collavre/cron_action_job.rb
|
|
325
325
|
- app/jobs/collavre/cron_scheduler_job.rb
|
|
326
326
|
- app/jobs/collavre/inbox_summary_job.rb
|
|
327
|
+
- app/jobs/collavre/merge_comments_job.rb
|
|
327
328
|
- app/jobs/collavre/permission_cache_cleanup_job.rb
|
|
328
329
|
- app/jobs/collavre/permission_cache_job.rb
|
|
329
330
|
- app/jobs/collavre/push_notification_job.rb
|
|
@@ -372,6 +373,7 @@ files:
|
|
|
372
373
|
- app/models/collavre/user_theme.rb
|
|
373
374
|
- app/models/collavre/variation.rb
|
|
374
375
|
- app/models/collavre/webauthn_credential.rb
|
|
376
|
+
- app/models/concerns/collavre/ai_agent_resolvable.rb
|
|
375
377
|
- app/services/collavre/ai_agent/a2a_dispatcher.rb
|
|
376
378
|
- app/services/collavre/ai_agent/agent_lifecycle_manager.rb
|
|
377
379
|
- app/services/collavre/ai_agent/approval_handler.rb
|