wco_models 3.1.0.302 → 3.1.0.304

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.txt +13 -0
  3. data/app/assets/javascripts/vendor/dynamicbones.mjs +1053 -0
  4. data/app/assets/javascripts/vendor/talkinghead.mjs +4825 -0
  5. data/app/controllers/wco/api_controller.rb +5 -2
  6. data/app/controllers/wco/application_controller.rb +31 -5
  7. data/app/controllers/wco/newsproducer_controller.rb +22 -0
  8. data/app/controllers/wco/newsvideos_controller.rb +1 -33
  9. data/app/controllers/wco/reports_controller.rb +21 -0
  10. data/app/helpers/wco/application_helper.rb +1 -1
  11. data/app/models/wco/ai_writer.rb +2 -1
  12. data/app/models/wco/leadset.rb +1 -1
  13. data/app/models/wco/newspartial.rb +10 -6
  14. data/app/models/wco/newsvideo.rb +46 -9
  15. data/app/models/wco/setting.rb +24 -0
  16. data/app/models/wco_email/email_filter.rb +1 -1
  17. data/app/views/wco/_main_footer.haml +2 -0
  18. data/app/views/wco/application/settings.haml +19 -0
  19. data/app/views/wco/newspartials/_show_in_newsvideo.haml +10 -4
  20. data/app/views/wco/newsproducer/_studio_1.js +279 -0
  21. data/app/views/wco/newsproducer/canvas.html.erb +263 -0
  22. data/app/views/wco/newsproducer/canvas_minimal.html.erb +132 -0
  23. data/app/views/wco/newsproducer/studio_1.html.erb +26 -0
  24. data/app/views/wco/reports/show.haml +9 -4
  25. data/config/initializers/assets.rb +2 -0
  26. data/config/routes.rb +15 -4
  27. data/lib/shortcuts.rb +4 -3
  28. metadata +27 -5
  29. data/config/initializers/ahoy.rb-trash +0 -12
  30. /data/app/views/wco/reports/{_to_facebook.html → _to_facebook_js.html} +0 -0
  31. /data/lib/{20260402_tmp.rb → done/20260402_migrate_email_filters.rb} +0 -0
@@ -24,8 +24,11 @@ class Wco::ApiController < ActionController::Base
24
24
  end
25
25
 
26
26
  def decode_simple_api_key
27
- if params[:api_key] === WCO_SIMPLE_API_KEY &&
28
- params[:api_secret] === WCO_SIMPLE_API_SECRET
27
+ if params[:api_key].present? &&
28
+ params[:api_secret].present? &&
29
+ params[:api_key] === Wco::Setting.get('WASYACO_SIMPLE_API_KEY') &&
30
+ params[:api_secret] === Wco::Setting.get('WASYACO_SIMPLE_API_SECRET')
31
+
29
32
  user = User.find_by({ email: 'piousbox@gmail.com' })
30
33
  sign_in user
31
34
  else
@@ -25,15 +25,14 @@ class Wco::ApplicationController < ActionController::Base
25
25
  out = 5 / 0
26
26
  end
27
27
 
28
- def home
29
- authorize! :home, Wco
28
+ def grapesjs
30
29
  end
31
30
 
32
- def tinymce
31
+ def home
33
32
  authorize! :home, Wco
34
- render layout: false
35
33
  end
36
34
 
35
+
37
36
  def linkedin_cb
38
37
  authorize! :open_permission, Wco
39
38
  code = params[:code]
@@ -76,6 +75,33 @@ class Wco::ApplicationController < ActionController::Base
76
75
  redirect_to url, allow_other_host: true
77
76
  end
78
77
 
78
+ def settings
79
+ authorize! :home, Wco
80
+ @settings = {}
81
+ Wco::Setting.all.to_a.map do |item|
82
+ @settings[item.key] = item.value
83
+ end
84
+ end
85
+
86
+ def set_settings
87
+ authorize! :home, Wco
88
+ params.fetch(:settings, {}).each do |key, value|
89
+ next unless Wco::Setting::KEYS.include?(key)
90
+
91
+ setting = Wco::Setting.find_or_initialize_by(key: key)
92
+ setting.value = value.strip
93
+ setting.save!
94
+ end
95
+
96
+ redirect_to settings_path, notice: 'Settings updated.'
97
+ end
98
+
99
+ def tinymce
100
+ authorize! :home, Wco
101
+ render layout: false
102
+ end
103
+
104
+
79
105
  ##
80
106
  ## private
81
107
  ##
@@ -125,7 +151,7 @@ class Wco::ApplicationController < ActionController::Base
125
151
  end
126
152
 
127
153
  def my_truthy? which
128
- ["1", "t", "T", "true"].include?( which )
154
+ ["1", "t", "true"].include?( which.downcase )
129
155
  end
130
156
 
131
157
  def set_lists
@@ -0,0 +1,22 @@
1
+
2
+ class Wco::NewsproducerController < Wco::ApplicationController
3
+
4
+ def canvas
5
+ authorize! :home, Wco
6
+ render layout: false
7
+ # render 'canvas_minimal', layout: false
8
+ end
9
+
10
+ def canvas_minimal
11
+ authorize! :home, Wco
12
+ render 'canvas_minimal', layout: false
13
+ end
14
+
15
+
16
+ def studio_1
17
+ authorize! :home, Wco
18
+ render layout: false
19
+ end
20
+
21
+ end
22
+
@@ -81,14 +81,7 @@ class Wco::NewsvideosController < Wco::ApplicationController
81
81
  return
82
82
  end
83
83
 
84
- sentences = PragmaticSegmenter::Segmenter.new(text: @newsvideo.body).segment
85
- phrases = sentences_to_phrases(sentences)
86
- puts! phrases, 'phrases'
87
-
88
- phrases.each do |phrase|
89
- newspartial = Wco::Newspartial.new body: phrase, newsvideo: @newsvideo
90
- newspartial.save!
91
- end
84
+ @newsvideo.do_split
92
85
 
93
86
  flash_notice 'Done spliting the video.'
94
87
  redirect_to request.referrer
@@ -113,31 +106,6 @@ class Wco::NewsvideosController < Wco::ApplicationController
113
106
  ##
114
107
  private
115
108
 
116
- def sentences_to_phrases sentences
117
- max_words = 50
118
- phrases = []
119
- current_phrase = []
120
-
121
- current_word_count = 0
122
-
123
- sentences.each do |sentence|
124
- words_in_sentence = sentence.split.size
125
-
126
- # If adding this sentence exceeds the limit, start a new phrase
127
- if current_word_count + words_in_sentence > max_words
128
- phrases << current_phrase.join(" ")
129
- current_phrase = []
130
- current_word_count = 0
131
- end
132
-
133
- current_phrase << sentence
134
- current_word_count += words_in_sentence
135
- end
136
-
137
- # Add the last phrase if any
138
- phrases << current_phrase.join(" ") unless current_phrase.empty?
139
- end
140
-
141
109
  def set_lists
142
110
  @tags_list = Wco::Tag.list
143
111
  end
@@ -52,6 +52,27 @@ class Wco::ReportsController < Wco::ApplicationController
52
52
  @new_report = Wco::Report.new
53
53
  end
54
54
 
55
+ def rewrite
56
+ @report = Wco::Report.unscoped.find params[:id]
57
+ authorize! :update, @report
58
+
59
+ out = Wco::AiWriter.do_call("You are a journalist writer. Write an article with the following title:", @report.title)
60
+ @report.update( body: out )
61
+
62
+ flash_notice 'Probably ok'
63
+ redirect_to request.referrer || '/'
64
+ end
65
+
66
+ def md_to_html
67
+ @report = Wco::Report.unscoped.find params[:id]
68
+ authorize! :update, @report
69
+
70
+ @report.update( body: CommonMarker.render_html(@report.body) )
71
+
72
+ flash_notice 'Probably ok'
73
+ redirect_to request.referrer || '/'
74
+ end
75
+
55
76
  def show
56
77
  @report = Wco::Report.unscoped.find params[:id]
57
78
  authorize! :show, @report
@@ -2,7 +2,7 @@
2
2
  module Wco::ApplicationHelper
3
3
 
4
4
  def pexels_search_path(opts)
5
- "/2025/pexels_example_169.html?pexels_api_key=#{PEXELS_API_KEY}&newsvideo_id=#{opts[:newsvideo_id]}"
5
+ "/2025/pexels_example_169.html?PEXELS_API_KEY=#{Wco::Setting.get('PEXELS_API_KEY')}&newsvideo_id=#{opts[:newsvideo_id]}"
6
6
  end
7
7
 
8
8
  def my_truthy? which
@@ -10,7 +10,7 @@ class Wco::AiWriter
10
10
  response = post(
11
11
  "/v1/chat/completions",
12
12
  headers: {
13
- "Authorization" => "Bearer #{OPENAI_API_KEY}",
13
+ "Authorization" => "Bearer #{Wco::Setting.get('OPENAI_API_KEY')}",
14
14
  "Content-Type" => "application/json"
15
15
  },
16
16
  body: {
@@ -27,6 +27,7 @@ class Wco::AiWriter
27
27
  ]
28
28
  }.to_json
29
29
  )
30
+ puts! response, 'response'
30
31
 
31
32
  response.dig("choices", 0, "message", "content")
32
33
  end
@@ -31,7 +31,7 @@ class Wco::Leadset
31
31
 
32
32
  field :email
33
33
  index({ email: 1 }, { name: 'email' })
34
- validates :email, uniqueness: { allow_nil: true } # presence: true
34
+ # validates :email, uniqueness: { allow_nil: true } # presence: true
35
35
 
36
36
 
37
37
  has_many :appliances, class_name: '::WcoHosting::Appliance', inverse_of: :leadset
@@ -10,6 +10,8 @@ class Wco::Newspartial
10
10
 
11
11
  PAGE_PARAM_NAME = 'newspartials_page'
12
12
 
13
+ MAX_WORDS = 20
14
+
13
15
  field :title
14
16
  # validates :title, presence: true, uniqueness: true
15
17
  # index({ title: 1 }, { unique: true })
@@ -58,7 +60,7 @@ class Wco::Newspartial
58
60
  end
59
61
 
60
62
  def generate_speech
61
- out = HTTParty.post( "#{HEAD_TTS_ORIGIN}/v1/synthesize",
63
+ out = HTTParty.post( "#{Wco::Setting.get('HEAD_TTS_ORIGIN')}/v1/synthesize",
62
64
  headers: {
63
65
  'Content-Type' => 'application/json',
64
66
  'Accept' => 'application/json',
@@ -70,7 +72,7 @@ class Wco::Newspartial
70
72
  }.to_json
71
73
  );
72
74
  out = out.body
73
- puts! out, 'out'
75
+ # puts! out, 'out'
74
76
 
75
77
  self[:config_json] = out
76
78
  tmp = JSON.parse( out )
@@ -93,13 +95,15 @@ class Wco::Newspartial
93
95
 
94
96
  ##
95
97
  ## Can I make do without puppet driver? Probably not: I need the audio worklet.
98
+ ## uses ishlib3js v0.0.2, not 0.1.0!!!
99
+ ## @deprecated, this is being brought into wco_models
96
100
  ##
97
101
  def generate_video
98
- cmd = "cd #{ISHLIB3JS_ROOT} ;
102
+ cmd = "cd #{Wco::Setting.get('ISHLIB3JS_ROOT')} ;
99
103
  node ./src/talking_head/example_puppeteer_wired.js \
100
- --api_key=#{WCO_SIMPLE_API_KEY} \
101
- --api_secret=#{WCO_SIMPLE_API_SECRET} \
102
- --wco_origin=#{WCO_ORIGIN} \
104
+ --api_key=#{Wco::Setting.get('WASYACO_SIMPLE_API_KEY')} \
105
+ --api_secret=#{Wco::Setting.get('WASYACO_SIMPLE_API_SECRET')} \
106
+ --wco_origin=#{Wco::Setting.get('WASYACO_ORIGIN')} \
103
107
  --w_px=#{newsvideo.w_px} \
104
108
  --h_px=#{newsvideo.h_px} \
105
109
  --newspartial_id=#{self[:id]} ";
@@ -51,11 +51,24 @@ class Wco::Newsvideo
51
51
  Wco::Newsoverlay.where( newsvideo_id: self.id )
52
52
  end
53
53
 
54
+ def do_split
55
+ @newsvideo = self
56
+
57
+ sentences = PragmaticSegmenter::Segmenter.new(text: @newsvideo.body).segment
58
+ phrases = sentences_to_phrases(sentences)
59
+
60
+ phrases.each do |phrase|
61
+ newspartial = Wco::Newspartial.new body: phrase, newsvideo: @newsvideo
62
+ newspartial.save!
63
+ end
64
+ end
65
+
66
+
54
67
  def generate
55
68
  @newsvideo = self
56
69
 
57
70
  ## put together config, first thing
58
- cmd = "cd #{Rails.root.join('tmp')} ; mkdir -p #{@newsvideo.id} ; cd #{@newsvideo.id} ; rm -f videolist.txt audiolist.txt ; "
71
+ cmd = "cd #{Rails.root.join('tmp')} ; rm -rf #{@newsvideo.id} ; mkdir -p #{@newsvideo.id} ; cd #{@newsvideo.id} ; "
59
72
  @newsvideo.newspartials.each_with_index do |part, idx|
60
73
  cmd = "#{cmd} echo \"file 'newspartial_#{idx}.mp4' \" >> videolist.txt ; "
61
74
  cmd = "#{cmd} echo \"file 'newspartial_#{idx}.wav' \" >> audiolist.txt ; "
@@ -88,11 +101,11 @@ class Wco::Newsvideo
88
101
  puts! out, 'out'
89
102
 
90
103
  ## video concat
91
- cmd = <<AOL
104
+ cmd = <<~AOL
92
105
  cd #{Rails.root.join('tmp', @newsvideo.id)} ;
93
106
  rm -f video_concat.mp4 ;
94
107
  ffmpeg -y -f concat -safe 0 -i videolist.txt -c copy video_concat.mp4 ;
95
- AOL
108
+ AOL
96
109
  puts "+++ video concat cmd:"
97
110
  puts cmd
98
111
  out = `#{cmd}`
@@ -100,22 +113,22 @@ AOL
100
113
 
101
114
  ## audio concat
102
115
  audio_filenames = (0...@newsvideo.newspartials.length).map { |i| "newspartial_#{i}.wav" }.join("|")
103
- cmd = <<AOL
116
+ cmd = <<~AOL
104
117
  cd #{Rails.root.join('tmp', @newsvideo.id)} ;
105
118
  rm -f audio_concat.wav ;
106
119
  ffmpeg -y -f concat -safe 0 -i audiolist.txt -c copy audio_concat.wav ;
107
- AOL
120
+ AOL
108
121
  puts "+++ audio concat cmd:"
109
122
  puts cmd
110
123
  out = `#{cmd}`
111
124
  puts! out, 'out'
112
125
 
113
126
  ## combine base
114
- cmd = <<AOL
127
+ cmd = <<~AOL
115
128
  cd #{Rails.root.join('tmp', @newsvideo.id)} ;
116
129
  rm -f output.mp4 ;
117
130
  ffmpeg -y -i video_concat.mp4 -i audio_concat.wav -c:v copy -c:a aac combined_base.mp4 ;
118
- AOL
131
+ AOL
119
132
  puts "+++ combine base cmd:"
120
133
  puts cmd
121
134
  out = `#{cmd}`
@@ -147,11 +160,11 @@ AOL
147
160
  puts ffmpeg_cmd
148
161
 
149
162
  # combine overlays 2
150
- cmd = <<AOL
163
+ cmd = <<~AOL
151
164
  cd #{Rails.root.join('tmp', @newsvideo.id)} ;
152
165
  rm -f combined_fin.mp4 ;
153
166
  #{ffmpeg_cmd} ;
154
- AOL
167
+ AOL
155
168
  puts "+++ ffmpeg cmd 2:"
156
169
  puts cmd
157
170
  out = `#{cmd}`
@@ -168,4 +181,28 @@ AOL
168
181
  end
169
182
  end
170
183
 
184
+ def sentences_to_phrases sentences
185
+ phrases = []
186
+ current_phrase = []
187
+
188
+ current_word_count = 0
189
+
190
+ sentences.each do |sentence|
191
+ words_in_sentence = sentence.split.size
192
+
193
+ # If adding this sentence exceeds the limit, start a new phrase
194
+ if current_word_count + words_in_sentence > Wco::Newspartial::MAX_WORDS
195
+ phrases << current_phrase.join(" ")
196
+ current_phrase = []
197
+ current_word_count = 0
198
+ end
199
+
200
+ current_phrase << sentence
201
+ current_word_count += words_in_sentence
202
+ end
203
+
204
+ # Add the last phrase if any
205
+ phrases << current_phrase.join(" ") unless current_phrase.empty?
206
+ end
207
+
171
208
  end
@@ -0,0 +1,24 @@
1
+
2
+ class Wco::Setting
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ store_in collection: 'wco_settings'
6
+
7
+ KEYS = [ 'HEAD_TTS_ORIGIN', 'ISHLIB3JS_ROOT',
8
+ 'OPENAI_API_KEY', 'PEXELS_API_KEY',
9
+ 'WASYACO_ORIGIN', 'WASYACO_SIMPLE_API_KEY', 'WASYACO_SIMPLE_API_SECRET',
10
+ ];
11
+
12
+ field :key
13
+ validates :key, inclusion: { in: KEYS }
14
+ validates :key, uniqueness: true
15
+
16
+ field :value
17
+
18
+ def self.get which
19
+ out = where( key: which ).first&.value
20
+ out ||= Object.const_defined?(which) && Object.const_get(which)
21
+ out ||= ENV[which]
22
+ end
23
+
24
+ end
@@ -9,7 +9,7 @@ class WcoEmail::EmailFilter
9
9
  PAGE_PARAM_NAME = :filters_page
10
10
 
11
11
  STATUS_ACTIVE = 'active'
12
- STATUS_INACTIVE = 'inactive'
12
+ STATUS_DISABLED = 'disabled'
13
13
  field :status, type: :string, default: STATUS_ACTIVE
14
14
  def self.active; where( :status.in => [ nil, STATUS_ACTIVE ] ); end
15
15
 
@@ -20,6 +20,7 @@
20
20
  .d-inline-block= button_to 'Logout', main_app.destroy_user_session_path, :method => :delete, data: { confirm: 'Are you sure?' }
21
21
  %li= link_to 'sidekiq', '/sidekiq', target: :_blank
22
22
  %li= link_to 'RTEditor', wco.application_tinymce_path, target: :_blank
23
+ %li= link_to 'GrapesJs', wco.application_grapesjs_path, target: :_blank
23
24
 
24
25
 
25
26
  .col-sm-4
@@ -29,6 +30,7 @@
29
30
  = pp_date Time.now
30
31
  = pp_time Time.now
31
32
  %li wco_models: v#{File.read(Gem.loaded_specs["wco_models"].full_gem_path + '/VERSION')}
33
+ %li= link_to 'settings', wco.settings_path
32
34
 
33
35
  .col-sm-4
34
36
  = render '/wco/profiles/form_extra', profile: @current_profile
@@ -0,0 +1,19 @@
1
+
2
+ .application-settings.maxwidth
3
+ %h5.text-center Settings
4
+
5
+ = form_with url: wco.settings_path do
6
+ %table.table
7
+ %thead
8
+ %tr
9
+ %th Key
10
+ %th Value
11
+ %tbody
12
+ - Wco::Setting::KEYS.each do |key|
13
+ %tr
14
+ %td
15
+ %strong= key
16
+ %td
17
+ = text_field_tag "settings[#{key}]", @settings[key], class: "form-control"
18
+
19
+ = submit_tag "UPDATE ALL", class: "btn btn-primary"
@@ -1,4 +1,5 @@
1
1
 
2
+
2
3
  .NewspartialTimelineW
3
4
  .zero-height.collapse-expand{ id: "#{newspartial.id.to_s}-wrapped-1" } <>
4
5
  .NewspartialTimeline{ style: "height: #{newspartial.duration_ms*HEIGHT_MS}px; min-height: 100px;" }
@@ -15,20 +16,25 @@
15
16
  .header.collapse-expand{ id: newspartial.id.to_s }
16
17
  Newspartial
17
18
  = link_to '[~]', edit_newspartial_path(newspartial)
18
- .d-inline-block= button_to 'x', newspartial_path(newspartial), method: :delete, :data => { :confirm => 'Are you sure?' }
19
19
  %span{ class: 'gray' } #{newspartial.id}
20
- = link_to '[api]', api_newspartial_config_path(newspartial, format: :json, api_key: WCO_SIMPLE_API_KEY, api_secret: WCO_SIMPLE_API_SECRET ), target: :_blank
20
+ = link_to '[api]', api_newspartial_config_path(newspartial, format: :json, api_key: Wco::Setting.get('WASYACO_SIMPLE_API_KEY'), api_secret: Wco::Setting.get('WASYACO_SIMPLE_API_SECRET') ), target: :_blank
21
21
 
22
22
  .full-textC
23
23
  .flex-row
24
+ .d-inline-block= button_to 'x', newspartial_path(newspartial), method: :delete, :data => { :confirm => 'Are you sure?' }
24
25
  .a Duration: `#{newspartial.duration_ms}`ms
25
26
  .descr= newspartial.body
26
27
  .d-flex.flex-column
27
- = button_to 'Generate video', newspartial_generate_video_path(newspartial), :data => { :confirm => 'Are you sure?' }
28
- .small.gray ISHLIB3JS_ROOT
28
+
29
+ -# = button_to 'Generate video', newspartial_generate_video_path(newspartial), :data => { :confirm => 'Are you sure?' }
30
+ -# .small.gray ISHLIB3JS_ROOT
31
+
32
+ = link_to 'Generate video 2', newsproducer_studio_1_path({ newspartial_id: newspartial.id.to_s, wco_origin: request.base_url, api_key: Wco::Setting.get('WASYACO_SIMPLE_API_KEY'), api_secret: Wco::Setting.get('WASYACO_SIMPLE_API_SECRET') }), class: 'btn btn-primary', data: { confirm: 'Are you sure?' }
33
+
29
34
  Video:
30
35
  - if newspartial.video
31
36
  = image_tag newspartial.video.thumb.url(:thumb), class: 'thumb'
37
+ = link_to 'view', video_path(newspartial.video)
32
38
  .d-flex.flex-column
33
39
  = button_to 'Generate speech', newspartial_generate_speech_path(newspartial), :data => { :confirm => 'Are you sure?' }
34
40
  .small.gray Requires HeadTTS.git on :8882