font-size: 0.875rem;
}
+.button.summary {
+ margin-top: 5px;
+}
+
.button.danger {
background-color: #dc3545;
color: var(--body-text);
redirect_to posts_path
end
+ def summarize
+ @post = Post.find(params[:id])
+ Rails.logger.debug "Attempting to summarize post #{@post.id} (#{@post.post_type})"
+
+ summarizer = KagiSummarizerService.new
+
+ # Determine what content to summarize based on post type
+ content_to_summarize = if @post.bookmark? && @post.url.present?
+ Rails.logger.debug "Using URL for summarization: #{@post.url}"
+ @post.url
+ elsif @post.content.present?
+ Rails.logger.debug "Using content for summarization (first 100 chars): #{@post.content.truncate(100)}"
+ @post.content
+ else
+ Rails.logger.warn "No content available for summarization"
+ nil
+ end
+
+ if content_to_summarize.nil?
+ redirect_to @post, alert: 'No content or URL available to summarize.'
+ return
+ end
+
+ type = @post.bookmark? && @post.url.present? ? :url : :text
+ result = summarizer.summarize(content_to_summarize, type)
+
+ Rails.logger.debug "Summarization result: #{result.inspect}"
+
+ if result && result['output'].present?
+ success = @post.update(summary: result['output'])
+ Rails.logger.debug "Update post with summary: #{success ? 'successful' : 'failed'}"
+ if success
+ redirect_to @post, notice: 'Summary generated successfully.'
+ else
+ Rails.logger.error "Failed to save summary: #{@post.errors.full_messages}"
+ redirect_to @post, alert: "Failed to save summary: #{@post.errors.full_messages.join(', ')}"
+ end
+ else
+ error_message = if result
+ "API returned success but no summary output was present"
+ else
+ "Failed to generate summary from API"
+ end
+ Rails.logger.error error_message
+ redirect_to @post, alert: error_message
+ end
+ end
private
# Use callbacks to share common setup or constraints between actions.
--- /dev/null
+class KagiSummarizerService
+ include HTTParty
+ base_uri 'https://kagi.com/api/v0'
+
+ def initialize
+ @options = {
+ headers: {
+ 'Authorization': "Bot #{Rails.application.credentials.kagi[:api_key]}",
+ 'Content-Type': 'application/json'
+ }
+ }
+ Rails.logger.debug "KagiSummarizerService initialized with options: #{@options.inspect}"
+ end
+
+ def summarize(content, type = :text)
+ Rails.logger.debug "Summarizing #{type} content: #{content.truncate(100)}"
+
+ body = if type == :url
+ {
+ url: content,
+ engine: 'agnes',
+ summary_type: 'summary'
+ }
+ else
+ {
+ text: content,
+ engine: 'agnes',
+ summary_type: 'summary'
+ }
+ end
+
+ Rails.logger.debug "Sending request to Kagi API with body: #{body.inspect}"
+
+ response = self.class.post('/summarize', @options.merge(body: body.to_json))
+ Rails.logger.debug "Received response from Kagi API: Status #{response.code}, Body: #{response.body}"
+
+ if response.success?
+ parsed_response = JSON.parse(response.body)
+ Rails.logger.debug "Parsed response: #{parsed_response.inspect}"
+ parsed_response
+ else
+ Rails.logger.error "Kagi API error: #{response.code} - #{response.body}"
+ Rails.logger.error "Full response object: #{response.inspect}"
+ nil
+ end
+ end
+end
<%= post.tags %>
</p>
+ <p>
+ <strong>Summary:</strong>
+ <%= post.summary %>
+ </p>
+
<p>
<strong>Content:</strong>
- <%= post.content %>
+ <%= if post&.content? then post.rendered_content.html_safe else "No content." end %>
</p>
<p>
<table>
<thead>
<tr>
- <th>Title</th>
- <th>Type</th>
- <th>Created</th>
- <th>Tags</th>
- <th>Actions</th>
+ <th style="width: 50%;">Title</th>
+ <th style="width: 10%;">Type</th>
+ <th style="width: 10%;">Created</th>
+ <th style="width: 10%;">Tags</th>
+ <th style="width: 20%;">Actions</th>
</tr>
</thead>
<tbody>
<%= link_to "View", post, class: "button small" %>
<%= link_to "Edit", edit_post_path(post), class: "button small" %>
<%= link_to "Delete", post, method: :delete, data: { confirm: "Are you sure you want to delete this post?" }, class: "button small danger" %>
+ <%= button_to "Generate Summary", summarize_post_path(post), class: "button small summary", data: { confirm: 'This will call the Kagi API. Continue?' } %>
</td>
</tr>
<% end %>
<div class="container">
<%= paginate @posts %>
</div>
-<% end %>
\ No newline at end of file
+<% end %>
<%= link_to "Back to posts", posts_path, class: "button" %>
</div>
<div class="post">
- <div class="container">
+ <div class="container">
<%= render @post %>
-
+
<div>
<%= link_to "Edit this post", edit_post_path(@post), class: "button" %>
-
+ <%= button_to "Summarise this post", summarize_post_path(@post), class: "button" %>
<%= button_to "Destroy this post", @post, method: :delete %>
</div>
</div>
-R6pLqAgOKbr+y07Is0RC9w3wQKbkr2h2KZO4RNpGCPAaE+X9ZK6i3AdB0XYfDiBRfvtj05EhYDJ+Yr72985yeeRhnppVqkwNK1mWp+SGfY+KVvaKI7HFYG8igI0LthDAdsyeoA2+ElpZgjHCCLqz36gRKSY/KEbT/y0NukHdQRzsWFHwqgY85vQK9gRfDBpM72h4iPwb9TOPvMtJuVwfaIAdcDY0OqaQpTlFIGjh/bT9njHuO2h4mJ6dCBJYoFR0+8oCpd8hHWHtxeB4bsqKspfxrNvFTYEDQUnNwJEbQGNl14577rhEshQ2WfgUVFGzTzfEpzQmFZZJtnWhJuJbgVaxUwH5STUKWEWNY5/gD2vgp1oj0aUO6eiyOjz5YCkUieR2RV3hVF+yvOMb9NTt2EBYjJGeoqngzKi/Hq0xnLHIRmkoFHPvuC1RHn0Ucg1Nez4cmWppoOK3xm/b0H2lKsWi97vkkii38lqtfmmRFtsdXf4AvFOQ2NWpFlLKLwHT87/bU32XIzPObwErw/wv9irxrBMAlDDqOTqtqd6k3liddoNGCqUqHLxSC0IoH/G2tdg1PzoKBvtIVyjtctYtUnta1vgjpz727WCSSYO6gDTOxTkRmwc++DcBAzOJFkYSz8gFm02TjHaBhzXQeqw=--fmjiwM3a6xuvqqTn--wwZXJd1Zcs5AJV/lQbpCtw==
\ No newline at end of file
+v2C864ebli2Zlrpg1eDeEn17NBNNghNeT86hcfSfebENXaStabVwZbEfgHTryFxZcISmAZE4rjjdcJcFSqTNQwvUvLGWaT3efR8WyZ2QQP14OSwL8+hnHDpDXLJrQIDUOHFF+aLaN0/uh3UPbFkBIf6eYyDVO+PP3jxb5bXsNC5wkqiCA/gBDuZoCLGrX9Pnw2nPWcXptVV2H2PVcQOaJDUcJl4yFJBSU3/cV/f9yFPV70cfrqUxU5hSHxHqfSZUF9bh/A6AAkvVWBOgJIETA3DiIS0fvoMSRK5uQuDWsaY7fdc947QXw4VrdvFSQqAegzzDjNYgLtZIqQ2GHDhaRWnmsVGf7IqA/6tKb6jGIc0G/Y7Mtdoa4s177dAdrMaIr+PM4ZMMbACYq1WbQWZ1IfMttyId0tAlRRlVbAFPOhFxElCHEWWlq1t3xGwgFDQOyfzX3P2Cr1FCeVXJb8VwGz32CceOna6BnhzRns6mQKt6VI+uAh6T2Uoe+krtM1jb6XT5+u0qZ8jo9P/iibCeeBZr9JBQ4rE+8AIvHOIZNuq7nZQDAHL+s9WtVrkvVjpRQ28dpccbjyQFvXhjqFUFIVGDTITi31FaHcjNBMhZyMKLtv/c9ASojRFrkBoDTgulGq8v+uEnW/3HPNkW+X3fqRXtjt1URKS+Qz57J7JOWt/EvX/f7PCG--pBijWvzkta+/36PZ--LnhGLSczKJbXJk9B8MyGRQ==
\ No newline at end of file
resources :pages, only: [:index, :show]
end
end
-
+
resources :mailing_lists, only: [:index] do
collection do
post 'subscribe'
get 'export', to: 'posts#export'
post 'import', to: 'posts#import'
resources :api_keys
- devise_for :users, controllers: {
- registrations: 'users/registrations',
+ devise_for :users, controllers: {
+ registrations: 'users/registrations',
confirmations: 'users/confirmations',
sessions: 'users/sessions'
}
get 'users/two_factor_authentication', to: 'users/two_factor_authentication#show'
post 'users/two_factor_authentication', to: 'users/two_factor_authentication#create'
- resources :posts
+
+ resources :posts do
+ member do
+ post :summarize
+ end
+ end
+
get '/feed', to: 'pubview#rss', as: 'rss', defaults: { format: 'rss' }
get '/feed/dispatches', to: 'pubview#dispatches_rss', as: 'dispatches_rss', defaults: { format: 'rss' }
get '/join', to: "pubview#join"
--- /dev/null
+class AddSummaryToPosts < ActiveRecord::Migration[7.2]
+ def change
+ add_column :posts, :summary, :text
+ end
+end
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.2].define(version: 2024_10_09_212849) do
+ActiveRecord::Schema[7.2].define(version: 2024_12_08_061729) do
create_table "api_keys", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "key"
t.datetime "created_at", null: false
t.string "url"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.text "summary"
t.index ["post_type"], name: "index_posts_on_post_type"
t.index ["published_at"], name: "index_posts_on_published_at"
t.index ["slug"], name: "index_posts_on_slug", unique: true