Compare commits

..

No commits in common. "d69a193e6deeba5597e64f3fbfa0a1a09a6c61ff" and "15d64b94b03b1599e85a8fedbf7c09375c4e3272" have entirely different histories.

21 changed files with 205 additions and 290 deletions

View File

@ -56,12 +56,10 @@ gem "ahoy_matey", "~> 5.2"
gem "ruby-openai", "~> 7.3" gem "ruby-openai", "~> 7.3"
gem "httparty", "~> 0.22.0" gem "httparty", "~> 0.22.0"
gem "down", "~> 5.4" gem "down", "~> 5.4"
gem "aws-sdk-s3", "~> 1.179" gem "aws-sdk-s3", "~> 1.177"
gem "sidekiq", "~> 7.3" gem "sidekiq", "~> 7.3"
gem "sidekiq-scheduler", "~> 5.0" gem "sidekiq-scheduler", "~> 5.0"
gem "image_processing", "~> 1.13"
group :development, :test do group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"

View File

@ -93,17 +93,17 @@ GEM
ruby2_keywords (>= 0.0.2) ruby2_keywords (>= 0.0.2)
ast (2.4.2) ast (2.4.2)
aws-eventstream (1.3.0) aws-eventstream (1.3.0)
aws-partitions (1.1043.0) aws-partitions (1.1035.0)
aws-sdk-core (3.217.0) aws-sdk-core (3.215.0)
aws-eventstream (~> 1, >= 1.3.0) aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0) aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9) aws-sigv4 (~> 1.9)
jmespath (~> 1, >= 1.6.1) jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.97.0) aws-sdk-kms (1.96.0)
aws-sdk-core (~> 3, >= 3.216.0) aws-sdk-core (~> 3, >= 3.210.0)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.179.0) aws-sdk-s3 (1.177.0)
aws-sdk-core (~> 3, >= 3.216.0) aws-sdk-core (~> 3, >= 3.210.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
aws-sigv4 (1.11.0) aws-sigv4 (1.11.0)
@ -164,14 +164,6 @@ GEM
multipart-post (~> 2.0) multipart-post (~> 2.0)
faraday-net_http (3.4.0) faraday-net_http (3.4.0)
net-http (>= 0.5.0) net-http (>= 0.5.0)
ffi (1.17.1-aarch64-linux-gnu)
ffi (1.17.1-aarch64-linux-musl)
ffi (1.17.1-arm-linux-gnu)
ffi (1.17.1-arm-linux-musl)
ffi (1.17.1-arm64-darwin)
ffi (1.17.1-x86_64-darwin)
ffi (1.17.1-x86_64-linux-gnu)
ffi (1.17.1-x86_64-linux-musl)
formtastic (5.0.0) formtastic (5.0.0)
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
formtastic_i18n (0.7.0) formtastic_i18n (0.7.0)
@ -189,19 +181,15 @@ GEM
csv csv
mini_mime (>= 1.0.0) mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
i18n (1.14.7) i18n (1.14.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
image_processing (1.13.0)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
inherited_resources (1.14.0) inherited_resources (1.14.0)
actionpack (>= 6.0) actionpack (>= 6.0)
has_scope (>= 0.6) has_scope (>= 0.6)
railties (>= 6.0) railties (>= 6.0)
responders (>= 2) responders (>= 2)
io-console (0.8.0) io-console (0.8.0)
irb (1.15.1) irb (1.14.3)
pp (>= 0.6.0)
rdoc (>= 4.0.0) rdoc (>= 4.0.0)
reline (>= 0.4.2) reline (>= 0.4.2)
jbuilder (2.13.0) jbuilder (2.13.0)
@ -252,7 +240,6 @@ GEM
matrix (0.4.2) matrix (0.4.2)
meta-tags (2.22.1) meta-tags (2.22.1)
actionpack (>= 6.0.0, < 8.1) actionpack (>= 6.0.0, < 8.1)
mini_magick (4.13.2)
mini_mime (1.1.5) mini_mime (1.1.5)
minitest (5.25.4) minitest (5.25.4)
msgpack (1.7.5) msgpack (1.7.5)
@ -276,21 +263,21 @@ GEM
net-protocol net-protocol
net-ssh (7.3.0) net-ssh (7.3.0)
nio4r (2.7.4) nio4r (2.7.4)
nokogiri (1.18.2-aarch64-linux-gnu) nokogiri (1.18.1-aarch64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.2-aarch64-linux-musl) nokogiri (1.18.1-aarch64-linux-musl)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.2-arm-linux-gnu) nokogiri (1.18.1-arm-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.2-arm-linux-musl) nokogiri (1.18.1-arm-linux-musl)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.2-arm64-darwin) nokogiri (1.18.1-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.2-x86_64-darwin) nokogiri (1.18.1-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.2-x86_64-linux-gnu) nokogiri (1.18.1-x86_64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.2-x86_64-linux-musl) nokogiri (1.18.1-x86_64-linux-musl)
racc (~> 1.4) racc (~> 1.4)
orm_adapter (0.5.0) orm_adapter (0.5.0)
ostruct (0.6.1) ostruct (0.6.1)
@ -299,9 +286,6 @@ GEM
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
pg (1.5.9) pg (1.5.9)
pp (0.6.2)
prettyprint
prettyprint (0.2.0)
propshaft (1.1.0) propshaft (1.1.0)
actionpack (>= 7.0.0) actionpack (>= 7.0.0)
activesupport (>= 7.0.0) activesupport (>= 7.0.0)
@ -311,7 +295,7 @@ GEM
date date
stringio stringio
public_suffix (6.0.1) public_suffix (6.0.1)
puma (6.6.0) puma (6.5.0)
nio4r (~> 2.0) nio4r (~> 2.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.8.1) racc (1.8.1)
@ -402,9 +386,6 @@ GEM
faraday (>= 1) faraday (>= 1)
faraday-multipart (>= 1) faraday-multipart (>= 1)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
ruby-vips (2.2.2)
ffi (~> 1.12)
logger
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
rubyzip (2.4.1) rubyzip (2.4.1)
rufus-scheduler (3.9.2) rufus-scheduler (3.9.2)
@ -429,7 +410,7 @@ GEM
tilt (>= 1.4.0, < 3) tilt (>= 1.4.0, < 3)
sitemap_generator (6.3.0) sitemap_generator (6.3.0)
builder (~> 3.0) builder (~> 3.0)
solid_cable (3.0.7) solid_cable (3.0.5)
actioncable (>= 7.2) actioncable (>= 7.2)
activejob (>= 7.2) activejob (>= 7.2)
activerecord (>= 7.2) activerecord (>= 7.2)
@ -438,7 +419,7 @@ GEM
activejob (>= 7.2) activejob (>= 7.2)
activerecord (>= 7.2) activerecord (>= 7.2)
railties (>= 7.2) railties (>= 7.2)
solid_queue (1.1.3) solid_queue (1.1.2)
activejob (>= 7.1) activejob (>= 7.1)
activerecord (>= 7.1) activerecord (>= 7.1)
concurrent-ruby (>= 1.3.1) concurrent-ruby (>= 1.3.1)
@ -511,7 +492,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
activeadmin (~> 3.2) activeadmin (~> 3.2)
ahoy_matey (~> 5.2) ahoy_matey (~> 5.2)
aws-sdk-s3 (~> 1.179) aws-sdk-s3 (~> 1.177)
bootsnap bootsnap
brakeman brakeman
capybara capybara
@ -521,7 +502,6 @@ DEPENDENCIES
down (~> 5.4) down (~> 5.4)
friendly_id (~> 5.5) friendly_id (~> 5.5)
httparty (~> 0.22.0) httparty (~> 0.22.0)
image_processing (~> 1.13)
jbuilder jbuilder
jsbundling-rails jsbundling-rails
kamal kamal

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -1,4 +1,3 @@
@import "photoswipe/dist/photoswipe.css";
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;

View File

@ -1,6 +1,6 @@
class HomeController < ApplicationController class HomeController < ApplicationController
def index def index
@latest_arts = WeatherArt.includes(:city).order(created_at: :desc).limit(20).shuffle.last(10) @latest_arts = WeatherArt.includes(:city).order(created_at: :desc).limit(6)
@featured_arts = WeatherArt.includes(:city).order(created_at: :desc).limit(5) @featured_arts = WeatherArt.includes(:city).order(created_at: :desc).limit(5)
set_meta_tags( set_meta_tags(
title: "AI-Generated Weather Art", title: "AI-Generated Weather Art",

View File

@ -1,6 +1,5 @@
// Entry point for the build script in your package.json // Entry point for the build script in your package.json
import "@hotwired/turbo-rails" import "@hotwired/turbo-rails"
import "@hotwired/stimulus"
import "@fontsource/playfair-display/400.css"; import "@fontsource/playfair-display/400.css";
import "@fontsource/playfair-display/700.css"; import "@fontsource/playfair-display/700.css";
import "@fontsource/raleway/400.css"; import "@fontsource/raleway/400.css";

View File

@ -6,9 +6,3 @@ import { application } from "./application"
import HelloController from "./hello_controller" import HelloController from "./hello_controller"
application.register("hello", HelloController) application.register("hello", HelloController)
import PhotoSwipeLightBoxController from "./photo_swipe_lightbox_controller"
console.log("ready to register photo-swipe")
application.register("photo-swipe-lightbox", PhotoSwipeLightBoxController)
console.log("successful to register photo-swipe")

View File

@ -1,42 +0,0 @@
import { Controller } from "@hotwired/stimulus"
import PhotoSwipeLightbox from 'photoswipe/lightbox'
import PhotoSwipe from 'photoswipe'
import 'photoswipe/dist/photoswipe.css'
export default class extends Controller {
static targets = ['image', 'gallery']
connect() {
this.initPhotoSwipeLightbox()
}
initPhotoSwipeLightbox() {
const lightbox = new PhotoSwipeLightbox({
gallery: this.galleryTarget,
children: 'a',
pswpModule: PhotoSwipe,
initialZoomInEndEvent: 'mousedown',
dataSource: (items) => {
return items.map((item) => ({
src: item.dataset.pswpSrc,
w: parseInt(item.dataset.pswpWidth, 10),
h: parseInt(item.dataset.pswpHeight, 10),
title: item.dataset.pswpCaption,
}))
},
padding: { top: 0, bottom: 0, left: 0, right: 0 }, // 自定义图片与页面边界的填充
closeOnScroll: false,
zoom: true, // 启用缩放功能
bgOpacity: 0.9, // 背景透明度
pswpUIOptions: {
arrowPrev: true,
arrowNext: true,
zoom: true, // 添加缩放按钮
fullscreen: true, // 添加全屏按钮
counter: true, // 显示当前图片编号
}
})
lightbox.init()
// console.log('PhotoSwipeLightbox instance:', lightbox);
}
}

View File

@ -4,7 +4,6 @@ class WeatherArt < ApplicationRecord
belongs_to :city belongs_to :city
has_one_attached :image has_one_attached :image
has_one_attached :image_with_watermark
has_many :visits, class_name: "Ahoy::Visit", foreign_key: :weather_art_id has_many :visits, class_name: "Ahoy::Visit", foreign_key: :weather_art_id
has_many :events, class_name: "Ahoy::Event", foreign_key: :weather_art_id has_many :events, class_name: "Ahoy::Event", foreign_key: :weather_art_id
@ -51,8 +50,4 @@ class WeatherArt < ApplicationRecord
Ahoy::Event.where("properties::jsonb->>'event_type' = 'weather_art_view' AND (properties::jsonb->>'weather_art_id')::integer = ?", self.id).count Ahoy::Event.where("properties::jsonb->>'event_type' = 'weather_art_view' AND (properties::jsonb->>'weather_art_id')::integer = ?", self.id).count
end end
end end
def image_url
image.attached? ? image.blob : nil
end
end end

View File

@ -50,7 +50,7 @@ class AiService
- Temperature: #{weather_data[:temperature]}°C - Temperature: #{weather_data[:temperature]}°C
- Weather: #{weather_data[:description]} - Weather: #{weather_data[:description]}
- Cloud cover: #{weather_data[:cloud]}% - Cloud cover: #{weather_data[:cloud]}%
- Time: #{weather_data[:time]} - Time: Early morning
Requirements: Requirements:
- Feature iconic landmarks or architecture from #{city.name} - Feature iconic landmarks or architecture from #{city.name}

View File

@ -31,8 +31,7 @@ class WeatherService
pressure: data["pressure"].to_f, pressure: data["pressure"].to_f,
visibility: data["vis"].to_f, visibility: data["vis"].to_f,
cloud: data["cloud"].to_f, cloud: data["cloud"].to_f,
description: data["text"], description: data["text"]
time: response["updateTime"]
} }
end end
end end

View File

@ -25,7 +25,7 @@
<!-- 最新天气艺术 --> <!-- 最新天气艺术 -->
<section class="container mx-auto px-4 py-16 space-y-12"> <section class="container mx-auto px-4 py-16 space-y-12">
<h2 class="text-3xl font-display font-bold text-center">Shuffle Latest Weather Art</h2> <h2 class="text-3xl font-display font-bold text-center">Latest Weather Art</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<% @latest_arts.each do |art| %> <% @latest_arts.each do |art| %>

View File

@ -33,22 +33,22 @@
<%# Includes all stylesheet files in app/assets/stylesheets %> <%# Includes all stylesheet files in app/assets/stylesheets %>
<%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %> <%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<!-- <script defer data-domain="todayaiweather.com" src="https://plausible.frytea.com/js/script.js"></script>--> <script defer data-domain="todayaiweather.com" src="https://plausible.frytea.com/js/script.js"></script>
<!-- <script defer src="https://busuanzi.frytea.com/js"></script>--> <script defer src="https://busuanzi.frytea.com/js"></script>
<!-- Google tag (gtag.js) --> <!-- Google tag (gtag.js) -->
<!-- <script async src="https://www.googletagmanager.com/gtag/js?id=G-PX1C92V5L7"></script>--> <script async src="https://www.googletagmanager.com/gtag/js?id=G-PX1C92V5L7"></script>
<!-- <script>--> <script>
<!-- window.dataLayer = window.dataLayer || [];--> window.dataLayer = window.dataLayer || [];
<!-- function gtag(){dataLayer.push(arguments);}--> function gtag(){dataLayer.push(arguments);}
<!-- gtag('js', new Date());--> gtag('js', new Date());
<!-- gtag('config', 'G-PX1C92V5L7');--> gtag('config', 'G-PX1C92V5L7');
<!-- </script>--> </script>
<!-- <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-7296634171837358"--> <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-7296634171837358"
<!-- crossorigin="anonymous"></script>--> crossorigin="anonymous"></script>
</head> </head>

View File

@ -12,7 +12,7 @@
<div class="join shadow-lg"> <div class="join shadow-lg">
<!-- 首页 --> <!-- 首页 -->
<%= link_to url_for(page: 1, region: params[:region], country: params[:country], sort: params[:sort]), <%= link_to url_for(page: 1, region: params[:region], country: params[:country], sort: params[:sort]),
class: "join-item btn btn-xs #{collection.first_page? ? 'btn-disabled' : 'btn-ghost'}" do %> class: "join-item btn #{collection.first_page? ? 'btn-disabled' : 'btn-ghost'}" do %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
</svg> </svg>
@ -20,7 +20,7 @@
<!-- 上一页 --> <!-- 上一页 -->
<%= link_to url_for(page: collection.prev_page || 1, region: params[:region], country: params[:country], sort: params[:sort]), <%= link_to url_for(page: collection.prev_page || 1, region: params[:region], country: params[:country], sort: params[:sort]),
class: "join-item btn btn-xs #{collection.first_page? ? 'btn-disabled' : 'btn-ghost'}" do %> class: "join-item btn #{collection.first_page? ? 'btn-disabled' : 'btn-ghost'}" do %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg> </svg>
@ -33,35 +33,35 @@
<% if start_page > 1 %> <% if start_page > 1 %>
<%= link_to 1, url_for(page: 1, region: params[:region], country: params[:country], sort: params[:sort]), <%= link_to 1, url_for(page: 1, region: params[:region], country: params[:country], sort: params[:sort]),
class: "join-item btn btn-xs btn-ghost hover:bg-primary/5" %> class: "join-item btn btn-ghost hover:bg-primary/5" %>
<% if start_page > 2 %> <% if start_page > 2 %>
<button class="join-item btn btn-xs btn-ghost btn-disabled">...</button> <button class="join-item btn btn-ghost btn-disabled">...</button>
<% end %> <% end %>
<% end %> <% end %>
<% (start_page..end_page).each do |page| %> <% (start_page..end_page).each do |page| %>
<% if page == collection.current_page %> <% if page == collection.current_page %>
<button class="join-item btn btn-xs btn-ghost bg-primary/10 font-medium"> <button class="join-item btn btn-ghost bg-primary/10 font-medium">
<%= page %> <%= page %>
</button> </button>
<% else %> <% else %>
<%= link_to page, url_for(page: page, region: params[:region], country: params[:country], sort: params[:sort]), <%= link_to page, url_for(page: page, region: params[:region], country: params[:country], sort: params[:sort]),
class: "join-item btn btn-xs btn-ghost hover:bg-primary/5" %> class: "join-item btn btn-ghost hover:bg-primary/5" %>
<% end %> <% end %>
<% end %> <% end %>
<% if end_page < collection.total_pages %> <% if end_page < collection.total_pages %>
<% if end_page < collection.total_pages - 1 %> <% if end_page < collection.total_pages - 1 %>
<button class="join-item btn btn-xs btn-ghost btn-disabled">...</button> <button class="join-item btn btn-ghost btn-disabled">...</button>
<% end %> <% end %>
<%= link_to collection.total_pages, <%= link_to collection.total_pages,
url_for(page: collection.total_pages, region: params[:region], country: params[:country], sort: params[:sort]), url_for(page: collection.total_pages, region: params[:region], country: params[:country], sort: params[:sort]),
class: "join-item btn btn-xs btn-ghost hover:bg-primary/5" %> class: "join-item btn btn-ghost hover:bg-primary/5" %>
<% end %> <% end %>
<!-- 下一页 --> <!-- 下一页 -->
<%= link_to url_for(page: collection.next_page || collection.total_pages, region: params[:region], country: params[:country], sort: params[:sort]), <%= link_to url_for(page: collection.next_page || collection.total_pages, region: params[:region], country: params[:country], sort: params[:sort]),
class: "join-item btn btn-xs #{collection.last_page? ? 'btn-disabled' : 'btn-ghost'}" do %> class: "join-item btn #{collection.last_page? ? 'btn-disabled' : 'btn-ghost'}" do %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg> </svg>
@ -69,7 +69,7 @@
<!-- 末页 --> <!-- 末页 -->
<%= link_to url_for(page: collection.total_pages, region: params[:region], country: params[:country], sort: params[:sort]), <%= link_to url_for(page: collection.total_pages, region: params[:region], country: params[:country], sort: params[:sort]),
class: "join-item btn btn-xs #{collection.last_page? ? 'btn-disabled' : 'btn-ghost'}" do %> class: "join-item btn #{collection.last_page? ? 'btn-disabled' : 'btn-ghost'}" do %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7" />
</svg> </svg>

View File

@ -0,0 +1,11 @@
<!-- app/views/weather_arts/_weather_stat.html.erb -->
<div class="stat bg-base-200/50 backdrop-blur-sm rounded-box hover:bg-base-300/50 transition-all duration-300">
<div class="flex items-center gap-2 mb-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<%= icon %>" />
</svg>
<div class="stat-title font-medium"><%= title %></div>
</div>
<div class="stat-value text-2xl"><%= value %></div>
<div class="stat-desc mt-1"><%= desc %></div>
</div>

View File

@ -1,37 +0,0 @@
<%# Partial _weather_stats.html.erb %>
<div class="stat bg-gradient-to-br from-primary/10 to-primary/20 hover:from-primary hover:to-primary/30 p-4 rounded-lg">
<div class="stat-title font-medium text-base">Temperature</div>
<div class="stat-value text-3xl"><%= weather_art.temperature %>°C</div>
<div class="stat-desc">Feels like <%= weather_art.feeling_temp %>°C</div>
</div>
<div class="stat bg-gradient-to-br from-secondary/10 to-secondary/20 hover:from-secondary hover:to-secondary/30 p-4 rounded-lg">
<div class="stat-title font-medium text-base">Wind</div>
<div class="stat-value text-3xl"><%= weather_art.wind_scale %></div>
<div class="stat-desc"><%= weather_art.wind_speed %> km/h</div>
</div>
<div class="stat bg-base-300 hover:bg-base-400 p-4 rounded-lg">
<div class="stat-title font-medium text-base">Humidity</div>
<div class="stat-value text-3xl"><%= weather_art.humidity %>%</div>
<div class="stat-desc">Relative humidity</div>
</div>
<div class="stat bg-base-300 hover:bg-base-400 p-4 rounded-lg">
<div class="stat-title font-medium text-base">Visibility</div>
<div class="stat-value text-3xl"><%= weather_art.visibility %> km</div>
<div class="stat-desc">Clear view distance</div>
</div>
<div class="stat bg-accent/10 hover:bg-accent p-4 rounded-lg">
<div class="stat-title font-medium text-base">Pressure</div>
<div class="stat-value text-3xl"><%= weather_art.pressure %> hPa</div>
<div class="stat-desc">Atmospheric pressure</div>
</div>
<div class="stat bg-base-200 hover:bg-base-100 p-4 rounded-lg">
<div class="stat-title font-medium text-base">Cloud Cover</div>
<div class="stat-value text-3xl"><%= weather_art.cloud %>%</div>
<div class="stat-desc">Sky coverage</div>
</div>

View File

@ -4,88 +4,155 @@
</script> </script>
<% end %> <% end %>
<div class="relative min-h-screen bg-white"> <!-- 使用更明快的背景颜色 --> <div class="relative min-h-screen bg-base-200">
<div class="container mx-auto px-4 pt-12 pb-16"> <!-- 背景图片 -->
<div class="max-w-6xl mx-auto space-y-6"> <% if @weather_art.image.attached? %>
<div class="fixed inset-0 -z-10">
<%= image_tag @weather_art.image,
class: "absolute w-full h-full object-cover scale-110 filter blur-2xl opacity-25" %>
<div class="absolute inset-0 bg-gradient-to-b from-base-200/90 to-base-200/70 backdrop-blur-md"></div>
</div>
<% end %>
<!-- 返回导航 --> <!-- 主要内容 -->
<div class="flex items-center"> <div class="relative z-10">
<%= link_to city_path(@weather_art.city), <!-- 返回导航 -->
class: "btn btn-ghost btn-md gap-2 bg-base-200 hover:bg-base-300 transition-all duration-300" do %> <div class="container mx-auto px-4 py-6">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <%= link_to city_path(@weather_art.city),
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /> class: "btn btn-ghost btn-lg gap-2 bg-base-100/50 backdrop-blur-sm hover:bg-base-100/70 transition-all duration-300" do %>
</svg> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
Back to <%= @weather_art.city.name %> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
<% end %> </svg>
</div> Back to <%= @weather_art.city.name %>
<% end %>
</div>
<!-- 主要内容 --> <div class="container mx-auto px-4 pb-16">
<div class="card bg-base-200/80 backdrop-blur-md shadow-lg overflow-hidden"> <!-- 调整透明度和阴影 --> <div class="max-w-6xl mx-auto">
<div class="grid lg:grid-cols-2 gap-6 items-center"> <!-- 头部信息 -->
<div class="text-center space-y-4 mb-12">
<div class="inline-flex items-center gap-2 text-sm font-medium px-4 py-2 rounded-full bg-base-100/50 backdrop-blur-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
</svg>
<%= @weather_art.city.full_name %>
</div>
<!-- 图片区域 --> <h1 class="text-4xl md:text-6xl font-display font-bold">
<% if @weather_art.image.attached? %> <span class="bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
<figure class="relative lg:h-[30rem] h-auto overflow-hidden rounded-lg"> <!-- 添加圆角 --> Weather Art
<div class="gallery" data-controller="photo-swipe-lightbox"> </span>
<div data-photo-swipe-lightbox-target="gallery" class="h-full"> </h1>
<% blob = @weather_art.image_blob %>
<%= link_to rails_blob_path(blob),
data: {
pswp_src: rails_blob_url(blob),
pswp_caption: 'Weather Art',
pswp_width: 1792,
pswp_height: 1024
} do %>
<%= image_tag @weather_art.image,
class: "object-cover w-full h-full transition-transform transform hover:scale-105 ease-in-out" %> <!-- 改变缩放效果 -->
<% end %>
</div>
</div>
</figure>
<% end %>
<!-- 信息区域 --> <div class="flex flex-wrap justify-center items-center gap-3">
<div class="card-body p-8 lg:py-10 lg:px-12"> <div class="badge badge-lg badge-primary gap-2">
<div class="prose max-w-none"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<h1 class="font-display text-4xl md:text-5xl font-bold text-gradient mb-6"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
Weather Art </svg>
</h1> <%= @weather_art.weather_date.strftime("%B %d, %Y") %>
</div>
<div class="badge badge-lg badge-secondary gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<%= @weather_art.weather_date.strftime("%H:%M") %>
</div>
</div>
</div>
<div class="flex flex-wrap gap-4 mb-6"> <!-- 主要卡片 -->
<div class="badge badge-lg badge-primary"> <div class="card lg:card-side bg-base-100/80 backdrop-blur-md shadow-2xl">
<%= @weather_art.weather_date.strftime("%B %d, %Y") %> <figure class="lg:w-1/2 relative aspect-square lg:aspect-auto group">
</div> <% if @weather_art.image.attached? %>
<div class="badge badge-lg badge-secondary"> <%= image_tag @weather_art.image,
<%= @weather_art.weather_date.strftime("%H:%M") %> class: "w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" %>
</div> <div class="absolute inset-0 bg-gradient-to-t from-base-100/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
</div> <% end %>
</figure>
<h2 class="text-2xl font-semibold mb-4"> <div class="card-body lg:w-1/2">
<div class="prose max-w-none mb-8">
<h2 class="card-title font-display text-3xl mb-4 flex items-center gap-3">
<%= weather_description_icon(@weather_art.description) %> <%= weather_description_icon(@weather_art.description) %>
<%= @weather_art.description %> <%= @weather_art.description %>
</h2> </h2>
<div class="divider"></div> <div class="divider"></div>
</div>
<div class="grid grid-cols-2 gap-4"> <!-- 天气数据网格 -->
<%= render 'weather_stats', weather_art: @weather_art %> <!-- 使用局部渲染 --> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="stat bg-base-200/50 backdrop-blur-sm rounded-box hover:bg-base-300/50 transition-all duration-300">
<div class="flex items-center gap-2 mb-2">
<%= weather_stat_icon("temperature") %>
<div class="stat-title font-medium">Temperature</div>
</div>
<div class="stat-value text-2xl"><%= @weather_art.temperature %>°C</div>
<div class="stat-desc mt-1">Feels like <%= @weather_art.feeling_temp %>°C</div>
</div>
<div class="stat bg-base-200/50 backdrop-blur-sm rounded-box hover:bg-base-300/50 transition-all duration-300">
<div class="flex items-center gap-2 mb-2">
<%= weather_stat_icon("wind") %>
<div class="stat-title font-medium">Wind</div>
</div>
<div class="stat-value text-2xl"><%= @weather_art.wind_scale %></div>
<div class="stat-desc mt-1"><%= @weather_art.wind_speed %> km/h</div>
</div>
<div class="stat bg-base-200/50 backdrop-blur-sm rounded-box hover:bg-base-300/50 transition-all duration-300">
<div class="flex items-center gap-2 mb-2">
<%= weather_stat_icon("humidity") %>
<div class="stat-title font-medium">Humidity</div>
</div>
<div class="stat-value text-2xl"><%= @weather_art.humidity %>%</div>
<div class="stat-desc mt-1">Relative humidity</div>
</div>
<div class="stat bg-base-200/50 backdrop-blur-sm rounded-box hover:bg-base-300/50 transition-all duration-300">
<div class="flex items-center gap-2 mb-2">
<%= weather_stat_icon("visibility") %>
<div class="stat-title font-medium">Visibility</div>
</div>
<div class="stat-value text-2xl"><%= @weather_art.visibility %> km</div>
<div class="stat-desc mt-1">Clear view distance</div>
</div>
<div class="stat bg-base-200/50 backdrop-blur-sm rounded-box hover:bg-base-300/50 transition-all duration-300">
<div class="flex items-center gap-2 mb-2">
<%= weather_stat_icon("pressure") %>
<div class="stat-title font-medium">Pressure</div>
</div>
<div class="stat-value text-2xl"><%= @weather_art.pressure %> hPa</div>
<div class="stat-desc mt-1">Atmospheric pressure</div>
</div>
<div class="stat bg-base-200/50 backdrop-blur-sm rounded-box hover:bg-base-300/50 transition-all duration-300">
<div class="flex items-center gap-2 mb-2">
<%= weather_stat_icon("cloud") %>
<div class="stat-title font-medium">Cloud Cover</div>
</div>
<div class="stat-value text-2xl"><%= @weather_art.cloud %>%</div>
<div class="stat-desc mt-1">Sky coverage</div>
</div>
</div>
<!-- AI Prompt -->
<div class="mt-8">
<div class="bg-base-200/50 backdrop-blur-sm p-6 rounded-box border border-base-300">
<div class="flex items-center gap-3 mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
<h3 class="font-display font-bold text-lg">AI Prompt</h3>
</div>
<p class="text-base-content/70 leading-relaxed">
<%= @weather_art.prompt %>
</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- AI Prompt -->
<div class="bg-primary/10 backdrop-blur-md p-6 rounded-lg border border-primary/20">
<div class="flex items-center gap-3 mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
<h3 class="font-display font-bold text-lg">AI Prompt</h3>
</div>
<p class="text-base-content/80 leading-relaxed">
<%= @weather_art.prompt %>
</p>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,53 +0,0 @@
class AddWatermarkToWeatherArtWorker
include Sidekiq::Worker
def perform(weather_art_id)
@weather_art = WeatherArt.find_by(id: weather_art_id)
return unless @weather_art
add_watermark
rescue StandardError => e
Rails.logger.error "Error adding watermark to WeatherArt #{weather_art_id}: #{e.message}"
Rails.logger.error e.backtrace.join("\n")
end
private
attr_reader :weather_art
def add_watermark
return if weather_art.image_with_watermark.attached?
watermark_path = Rails.root.join("app/assets/images/today_ai_weather_copyright_watermark1.png")
return unless File.exist?(watermark_path)
image_tempfile = nil
watermark_tempfile = nil
begin
image_tempfile = weather_art.image.download
return unless image_dimensions_are_sufficient?(image_tempfile.path)
image = ImageProcessing::Vips.source(image_tempfile.path)
watermark = ImageProcessing::Vips.source(watermark_path)
watermarked_image = image.composite(watermark, "overlay")
watermark_tempfile = Tempfile.new([ "watermarked_image", ".png" ])
watermarked_image.write_to_file(watermark_tempfile.path)
weather_art.image_with_watermark.attach(
io: File.open(watermark_tempfile.path),
filename: "#{generate_filename("watermarked")}",
content_type: "image/png"
)
ensure
watermark_tempfile.unlink if watermark_tempfile
end
end
def image_dimensions_are_sufficient?(image_path)
dimensions = ImageProcessing::Vips.source(image_path).sizes
dimensions.width >= 200 && dimensions.height >= 200
end
def generate_filename(prefix)
"#{prefix}_#{weather_art.image.filename.base}"
end
end

View File

@ -1,16 +1,19 @@
class BatchGenerateWeatherArtsWorker class BatchGenerateWeatherArtsWorker
include Sidekiq::Worker include Sidekiq::Worker
GENERATION_INTERVAL = 24.hours GENERATION_INTERVAL = 24.hours
MAX_DURATION = 50.minutes MAX_DURATION = 50.minutes
SLEEP_DURATION = 120.seconds SLEEP_DURATION = 120.seconds
BATCH_SIZE = 20
def perform(*args) def perform(*args)
start_time = Time.current start_time = Time.current
cities_to_process = get_eligible_cities.shuffle.take(BATCH_SIZE)
cities_to_process = get_eligible_cities
cities_to_process.each do |city| cities_to_process.each do |city|
break if Time.current - start_time > MAX_DURATION break if Time.current - start_time > MAX_DURATION
Rails.logger.info "Generating weather art for #{city.name}" Rails.logger.info "Generating weather art for #{city.name}"
GenerateWeatherArtWorker.perform_async(city.id) GenerateWeatherArtWorker.perform_async(city.id)
sleep SLEEP_DURATION sleep SLEEP_DURATION
end end
@ -20,6 +23,7 @@ class BatchGenerateWeatherArtsWorker
def get_eligible_cities def get_eligible_cities
cutoff_time = Time.current - GENERATION_INTERVAL cutoff_time = Time.current - GENERATION_INTERVAL
City.active City.active
.joins("LEFT JOIN ( .joins("LEFT JOIN (
SELECT city_id, MAX(created_at) as last_generation_time SELECT city_id, MAX(created_at) as last_generation_time

View File

@ -18,7 +18,6 @@
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"jquery": "^3.7.1", "jquery": "^3.7.1",
"jquery-ui": "^1.14.1", "jquery-ui": "^1.14.1",
"photoswipe": "^5.4.4",
"postcss": "^8.5.1", "postcss": "^8.5.1",
"sass": "^1.83.4", "sass": "^1.83.4",
"tailwindcss": "^3.4.17" "tailwindcss": "^3.4.17"

View File

@ -153,7 +153,7 @@
"@hotwired/stimulus@^3.2.2": "@hotwired/stimulus@^3.2.2":
version "3.2.2" version "3.2.2"
resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.2.2.tgz#071aab59c600fed95b97939e605ff261a4251608" resolved "https://registry.npmjs.org/@hotwired/stimulus/-/stimulus-3.2.2.tgz"
integrity sha512-eGeIqNOQpXoPAIP7tC1+1Yc1yl1xnwYqg+3mzqxyrbE5pg5YFBZcA6YoTiByJB6DKAEsiWtl6tjTJS4IYtbB7A== integrity sha512-eGeIqNOQpXoPAIP7tC1+1Yc1yl1xnwYqg+3mzqxyrbE5pg5YFBZcA6YoTiByJB6DKAEsiWtl6tjTJS4IYtbB7A==
"@hotwired/turbo-rails@^8.0.12": "@hotwired/turbo-rails@^8.0.12":
@ -722,13 +722,20 @@ jiti@^1.21.6:
resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz" resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz"
integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==
jquery-ui@^1.13.3, jquery-ui@^1.14.1: jquery-ui@^1.13.3:
version "1.14.1" version "1.14.1"
resolved "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.14.1.tgz" resolved "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.14.1.tgz"
integrity sha512-DhzsYH8VeIvOaxwi+B/2BCsFFT5EGjShdzOcm5DssWjtcpGWIMsn66rJciDA6jBruzNiLf1q0KvwMoX1uGNvnQ== integrity sha512-DhzsYH8VeIvOaxwi+B/2BCsFFT5EGjShdzOcm5DssWjtcpGWIMsn66rJciDA6jBruzNiLf1q0KvwMoX1uGNvnQ==
dependencies: dependencies:
jquery ">=1.12.0 <5.0.0" jquery ">=1.12.0 <5.0.0"
jquery-ui@^1.14.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.14.1.tgz#ba342ea3ffff662b787595391f607d923313e040"
integrity sha512-DhzsYH8VeIvOaxwi+B/2BCsFFT5EGjShdzOcm5DssWjtcpGWIMsn66rJciDA6jBruzNiLf1q0KvwMoX1uGNvnQ==
dependencies:
jquery ">=1.12.0 <5.0.0"
jquery-ujs@^1.2.2: jquery-ujs@^1.2.2:
version "1.2.3" version "1.2.3"
resolved "https://registry.npmjs.org/jquery-ujs/-/jquery-ujs-1.2.3.tgz" resolved "https://registry.npmjs.org/jquery-ujs/-/jquery-ujs-1.2.3.tgz"
@ -846,11 +853,6 @@ path-scurry@^1.11.1:
lru-cache "^10.2.0" lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
photoswipe@^5.4.4:
version "5.4.4"
resolved "https://registry.yarnpkg.com/photoswipe/-/photoswipe-5.4.4.tgz#e045dc036453493188d5c8665b0e8f1000ac4d6e"
integrity sha512-WNFHoKrkZNnvFFhbHL93WDkW3ifwVOXSW3w1UuZZelSmgXpIGiZSNlZJq37rR8YejqME2rHs9EhH9ZvlvFH2NA==
picocolors@^1, picocolors@^1.0.1, picocolors@^1.1.1: picocolors@^1, picocolors@^1.0.1, picocolors@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz"