@@ -6,7 +6,6 @@ class PromptMessagesBuilder
66 MAX_CHAT_UPLOADS = 5
77 MAX_TOPIC_UPLOADS = 5
88 attr_reader :chat_context_posts
9- attr_reader :chat_context_post_upload_ids
109 attr_accessor :topic
1110
1211 def self . messages_from_chat (
@@ -113,12 +112,13 @@ def self.messages_from_post(post, style: nil, max_posts:, bot_usernames:, includ
113112 FROM upload_references ref
114113 WHERE ref.target_type = 'Post' AND ref.target_id = posts.id
115114 ) as upload_ids" ,
115+ "posts.created_at" ,
116116 )
117117
118118 builder = new
119119 builder . topic = post . topic
120120
121- context . reverse_each do |raw , username , custom_prompt , upload_ids |
121+ context . reverse_each do |raw , username , custom_prompt , upload_ids , created_at |
122122 custom_prompt_translation =
123123 Proc . new do |message |
124124 # We can't keep backwards-compatibility for stored functions.
@@ -134,6 +134,7 @@ def self.messages_from_post(post, style: nil, max_posts:, bot_usernames:, includ
134134
135135 thinking = message [ 4 ]
136136 custom_context [ :thinking ] = thinking if thinking
137+ custom_context [ :created_at ] = created_at
137138
138139 builder . push ( **custom_context )
139140 end
@@ -149,6 +150,7 @@ def self.messages_from_post(post, style: nil, max_posts:, bot_usernames:, includ
149150 if upload_ids . present? && context [ :type ] == :user && include_uploads
150151 context [ :upload_ids ] = upload_ids . compact
151152 end
153+ context [ :created_at ] = created_at
152154
153155 builder . push ( **context )
154156 end
@@ -159,6 +161,7 @@ def self.messages_from_post(post, style: nil, max_posts:, bot_usernames:, includ
159161
160162 def initialize
161163 @raw_messages = [ ]
164+ @timestamps = { }
162165 end
163166
164167 def set_chat_context_posts ( post_ids , guardian , include_uploads :)
@@ -171,35 +174,74 @@ def set_chat_context_posts(post_ids, guardian, include_uploads:)
171174 posts << post
172175 end
173176 if posts . present?
174- posts_context =
175- + "\n This chat is in the context of the Discourse topic '#{ posts [ 0 ] . topic . title } ':\n \n "
176- posts_context = + "{{{\n "
177+ posts_context = [ ]
178+ posts_context << "\n This chat is in the context of the Discourse topic '#{ posts [ 0 ] . topic . title } ':\n \n "
179+ posts_context << "{{{\n "
177180 posts . each do |post |
178181 posts_context << "url: #{ post . url } \n "
179182 posts_context << "#{ post . username } : #{ post . raw } \n \n "
183+ if include_uploads
184+ post . uploads . each { |upload | posts_context << { upload_id : upload . id } }
185+ end
180186 end
181187 posts_context << "}}}"
182188 @chat_context_posts = posts_context
183- if include_uploads
184- uploads = [ ]
185- posts . each { |post | uploads . concat ( post . uploads . pluck ( :id ) ) }
186- uploads . uniq!
187- @chat_context_post_upload_ids = uploads . take ( MAX_CHAT_UPLOADS )
188- end
189189 end
190190 end
191191
192192 def to_a ( limit : nil , style : nil )
193+ # topic and chat array are special, they are single messages that contain all history
193194 return chat_array ( limit : limit ) if style == :chat
194195 return topic_array if style == :topic
196+
197+ # the rest of the styles can include multiple messages
198+ result = valid_messages_array ( @raw_messages )
199+ prepend_chat_post_context ( result ) if style == :chat_with_context
200+
201+ if limit
202+ result [ 0 ..limit ]
203+ else
204+ result
205+ end
206+ end
207+
208+ def push ( type :, content :, name : nil , upload_ids : nil , id : nil , thinking : nil , created_at : nil )
209+ if !%i[ user model tool tool_call system ] . include? ( type )
210+ raise ArgumentError , "type must be either :user, :model, :tool, :tool_call or :system"
211+ end
212+ raise ArgumentError , "upload_ids must be an array" if upload_ids && !upload_ids . is_a? ( Array )
213+
214+ content = [ content , *upload_ids . map { |upload_id | { upload_id : upload_id } } ] if upload_ids
215+ message = { type : type , content : content }
216+ message [ :name ] = name . to_s if name
217+ message [ :id ] = id . to_s if id
218+ if thinking
219+ message [ :thinking ] = thinking [ "thinking" ] if thinking [ "thinking" ]
220+ message [ :thinking_signature ] = thinking [ "thinking_signature" ] if thinking [
221+ "thinking_signature"
222+ ]
223+ message [ :redacted_thinking_signature ] = thinking [
224+ "redacted_thinking_signature"
225+ ] if thinking [ "redacted_thinking_signature" ]
226+ end
227+
228+ @raw_messages << message
229+ @timestamps [ message ] = created_at if created_at
230+
231+ message
232+ end
233+
234+ private
235+
236+ def valid_messages_array ( messages )
195237 result = [ ]
196238
197239 # this will create a "valid" messages array
198240 # 1. ensures we always start with a user message
199241 # 2. ensures we always end with a user message
200242 # 3. ensures we always interleave user and model messages
201243 last_type = nil
202- @raw_messages . each do |message |
244+ messages . each do |message |
203245 next if !last_type && message [ :type ] != :user
204246
205247 if last_type == :tool_call && message [ :type ] != :tool
@@ -239,51 +281,26 @@ def to_a(limit: nil, style: nil)
239281 last_type = message [ :type ]
240282 end
241283
242- if style == :chat_with_context && @chat_context_posts
243- buffer = +"You are replying inside a Discourse chat."
244- buffer << "\n "
245- buffer << @chat_context_posts
246- buffer << "\n "
247- buffer << "Your instructions are:\n "
248- result [ 0 ] [ :content ] = "#{ buffer } #{ result [ 0 ] [ :content ] } "
249- if @chat_context_post_upload_ids . present?
250- result [ 0 ] [ :upload_ids ] = ( result [ 0 ] [ :upload_ids ] || [ ] ) . concat (
251- @chat_context_post_upload_ids ,
252- )
253- end
254- end
255-
256- if limit
257- result [ 0 ..limit ]
258- else
259- result
260- end
284+ result
261285 end
262286
263- def push ( type :, content :, name : nil , upload_ids : nil , id : nil , thinking : nil )
264- if !%i[ user model tool tool_call system ] . include? ( type )
265- raise ArgumentError , "type must be either :user, :model, :tool, :tool_call or :system"
266- end
267- raise ArgumentError , "upload_ids must be an array" if upload_ids && !upload_ids . is_a? ( Array )
287+ def prepend_chat_post_context ( messages )
288+ return if @chat_context_posts . blank?
268289
269- content = [ content , *upload_ids . map { |upload_id | { upload_id : upload_id } } ] if upload_ids
270- message = { type : type , content : content }
271- message [ :name ] = name . to_s if name
272- message [ :id ] = id . to_s if id
273- if thinking
274- message [ :thinking ] = thinking [ "thinking" ] if thinking [ "thinking" ]
275- message [ :thinking_signature ] = thinking [ "thinking_signature" ] if thinking [
276- "thinking_signature"
277- ]
278- message [ :redacted_thinking_signature ] = thinking [
279- "redacted_thinking_signature"
280- ] if thinking [ "redacted_thinking_signature" ]
281- end
290+ old_content = messages [ 0 ] [ :content ]
291+ old_content = [ old_content ] if !old_content . is_a? ( Array )
282292
283- @raw_messages << message
284- end
293+ new_content = [ ]
294+ new_content << "You are replying inside a Discourse chat.\n "
295+ new_content . concat ( @chat_context_posts )
296+ new_content << "\n "
297+ new_content << "Your instructions are:\n "
298+ new_content . concat ( old_content )
285299
286- private
300+ compressed = compress_messages_buffer ( new_content . flatten , max_uploads : MAX_CHAT_UPLOADS )
301+
302+ messages [ 0 ] [ :content ] = compressed
303+ end
287304
288305 def format_user_info ( user )
289306 info = [ ]
@@ -294,6 +311,34 @@ def format_user_info(user)
294311 "#{ user . username } (#{ user . name } ): #{ info . compact . join ( ", " ) } "
295312 end
296313
314+ def format_timestamp ( timestamp )
315+ return nil unless timestamp
316+
317+ time_diff = Time . now - timestamp
318+
319+ if time_diff < 1 . minute
320+ "just now"
321+ elsif time_diff < 1 . hour
322+ mins = ( time_diff / 1 . minute ) . round
323+ "#{ mins } #{ mins == 1 ? "minute" : "minutes" } ago"
324+ elsif time_diff < 1 . day
325+ hours = ( time_diff / 1 . hour ) . round
326+ "#{ hours } #{ hours == 1 ? "hour" : "hours" } ago"
327+ elsif time_diff < 7 . days
328+ days = ( time_diff / 1 . day ) . round
329+ "#{ days } #{ days == 1 ? "day" : "days" } ago"
330+ elsif time_diff < 30 . days
331+ weeks = ( time_diff / 7 . days ) . round
332+ "#{ weeks } #{ weeks == 1 ? "week" : "weeks" } ago"
333+ elsif time_diff < 365 . days
334+ months = ( time_diff / 30 . days ) . round
335+ "#{ months } #{ months == 1 ? "month" : "months" } ago"
336+ else
337+ years = ( time_diff / 365 . days ) . round
338+ "#{ years } #{ years == 1 ? "year" : "years" } ago"
339+ end
340+ end
341+
297342 def user_role ( user )
298343 return "moderator" if user . moderator?
299344 return "admin" if user . admin?
@@ -323,45 +368,57 @@ def account_age(user)
323368 end
324369 end
325370
326- def topic_array
327- raw_messages = @raw_messages . dup
371+ def format_topic_info ( topic )
328372 content_array = [ ]
329- content_array << "You are operating in a Discourse forum.\n \n "
330373
331- if @topic
332- if @topic . private_message?
333- content_array << "Private message info.\n "
334- else
335- content_array << "Topic information:\n "
336- end
374+ if topic . private_message?
375+ content_array << "Private message info.\n "
376+ else
377+ content_array << "Topic information:\n "
378+ end
337379
338- content_array << "- URL: #{ @topic . url } \n "
339- content_array << "- Title: #{ @topic . title } \n "
340- if SiteSetting . tagging_enabled
341- tags = @topic . tags . pluck ( :name )
342- tags -= DiscourseTagging . hidden_tag_names if tags . present?
343- content_array << "- Tags: #{ tags . join ( ", " ) } \n " if tags . present?
344- end
345- if !@topic . private_message?
346- content_array << "- Category: #{ @topic . category . name } \n " if @topic . category
347- end
348- content_array << "- Number of replies: #{ @topic . posts_count - 1 } \n \n "
380+ content_array << "- URL: #{ topic . url } \n "
381+ content_array << "- Title: #{ topic . title } \n "
382+ if SiteSetting . tagging_enabled
383+ tags = topic . tags . pluck ( :name )
384+ tags -= DiscourseTagging . hidden_tag_names if tags . present?
385+ content_array << "- Tags: #{ tags . join ( ", " ) } \n " if tags . present?
386+ end
387+ if !topic . private_message?
388+ content_array << "- Category: #{ topic . category . name } \n " if topic . category
389+ end
390+ content_array << "- Number of replies: #{ topic . posts_count - 1 } \n \n "
391+
392+ content_array . join
393+ end
394+
395+ def format_user_infos ( usernames )
396+ content_array = [ ]
397+
398+ if usernames . present?
399+ users_details =
400+ User
401+ . where ( username : usernames )
402+ . includes ( :user_stat )
403+ . map { |user | format_user_info ( user ) }
404+ . compact
405+ content_array << "User information:\n "
406+ content_array << "- #{ users_details . join ( "\n - " ) } \n \n " if users_details . present?
349407 end
408+ content_array . join
409+ end
410+
411+ def topic_array
412+ raw_messages = @raw_messages . dup
413+ content_array = [ ]
414+ content_array << "You are operating in a Discourse forum.\n \n "
415+ content_array << format_topic_info ( @topic ) if @topic
350416
351417 if raw_messages . present?
352418 usernames =
353419 raw_messages . filter { |message | message [ :type ] == :user } . map { |message | message [ :id ] }
354420
355- if usernames . present?
356- users_details =
357- User
358- . where ( username : usernames )
359- . includes ( :user_stat )
360- . map { |user | format_user_info ( user ) }
361- . compact
362- content_array << "User information:\n "
363- content_array << "- #{ users_details . join ( "\n - " ) } \n \n " if users_details . present?
364- end
421+ content_array << format_user_infos ( usernames ) if usernames . present?
365422 end
366423
367424 last_user_message = raw_messages . pop
@@ -370,6 +427,8 @@ def topic_array
370427 content_array << "Here is the conversation so far:\n "
371428 raw_messages . each do |message |
372429 content_array << "#{ message [ :id ] || "User" } : "
430+ timestamp = @timestamps [ message ]
431+ content_array << "(#{ format_timestamp ( timestamp ) } ) " if timestamp
373432 content_array << message [ :content ]
374433 content_array << "\n \n "
375434 end
0 commit comments