feat: add AI-generated city descriptions

- Implement method to get multilingual city descriptions
- Create worker for generating descriptions in the background
- Update database schema to include description translations
- Update AiService to fetch descriptions from City model

This commit introduces a feature to generate city descriptions
in multiple languages using AI. It includes methods for
caching descriptions and a background job to handle
generation to improve performance and user experience.
This commit is contained in:
songtianlun 2025-04-12 14:03:48 +08:00
parent 371b534c48
commit b0b64c2fe3
6 changed files with 113 additions and 16 deletions

View File

@ -249,4 +249,81 @@ class City < ApplicationRecord
"Unknown #{type}"
end
end
# 获取城市描述,支持多语言
def get_description(locale = I18n.locale.to_s, force_refresh = false)
# 标准化语言代码为小写字符串
locale = locale.to_s.downcase
# 从text字段解析JSON数据
cached_descriptions = get_description_hash
# 如果请求的语言版本存在,直接返回
return cached_descriptions[locale] if cached_descriptions[locale].present? && !force_refresh
# 如果有英文版本且请求的不是英文,返回英文版本
return cached_descriptions['en'] if locale != 'en' && cached_descriptions['en'].present? && !force_refresh
# 否则,生成新的描述并保存
new_description = generate_ai_description(locale)
# 更新数据库中的描述
update_description(locale, new_description)
new_description
end
# 获取描述的JSON哈希
def get_description_hash
begin
# 尝试解析存储的JSON字符串
JSON.parse(description_translations || "{}")
rescue JSON::ParserError
# 如果解析失败,返回空哈希
{}
end
end
# 更新特定语言的城市描述
def update_description(locale, new_description)
# 标准化语言代码
locale = locale.to_s.downcase
# 获取当前描述哈希并更新
updated_descriptions = get_description_hash
updated_descriptions[locale] = new_description
# 将哈希转换回JSON字符串并更新数据库
update(description_translations: updated_descriptions.to_json)
# 返回更新后的描述
new_description
end
# 使用AI生成城市描述
def generate_ai_description(locale = 'en')
# 暂时只支持英文生成
return generate_en_description if locale == 'en'
# 其他语言暂不支持,默认返回英文描述
generate_en_description
end
private
# 生成英文描述
def generate_en_description
region_name = country&.region&.name || ""
country_name = country&.name || ""
state_name = state&.name || ""
# 创建AI服务实例
ai_service = AiService.new
# 生成描述
system_message = "You are a global geography master, you understand the culture, geography, architecture, customs and other information of all cities around the world. Describe this city based on the city I gave you. Include details about its culture, climate, landmarks, and any unique features that make this place special. Condense the keyword into a description of about 50 words"
user_message = "Describe the characteristics of the city of #{name}, located in the #{state_name}, #{country_name}, #{region_name}"
ai_service.ask_ai(system_message, user_message)
end
end

View File

@ -18,7 +18,8 @@ class AiService
end
def generate_prompt(city, weather_data)
city_desc = generate_location_desc(city)
# city_desc = generate_location_desc(city)
city_desc = city.get_description('en')
system_message =
"You are a professional artist creating prompts for image generation. Create realistic, artistic weather scenes featuring iconic landmarks."
@ -26,17 +27,17 @@ class AiService
ask_ai(system_message, user_message)
end
def generate_location_desc(city)
region = city.country.region&.name || ""
country = city.country.name || ""
state = city.state&.name || ""
# def generate_location_desc(city)
# region = city.country.region&.name || ""
# country = city.country.name || ""
# state = city.state&.name || ""
system_message =
"You are a global geography master, you understand the culture, geography, architecture, customs and other information of all cities around the world. Describe this city based on the city I gave you. Include details about its culture, climate, landmarks, and any unique features that make this place special. Condense the keyword into a description of about 50 words"
user_message =
"Describe the characteristics of the city of #{city.name}, located in the #{state}, #{country}, #{region}"
ask_ai(system_message, user_message)
end
# system_message =
# "You are a global geography master, you understand the culture, geography, architecture, customs and other information of all cities around the world. Describe this city based on the city I gave you. Include details about its culture, climate, landmarks, and any unique features that make this place special. Condense the keyword into a description of about 50 words"
# user_message =
# "Describe the characteristics of the city of #{city.name}, located in the #{state}, #{country}, #{region}"
# ask_ai(system_message, user_message)
# end
def generate_image(prompt, model_type = :flux)
case model_type
@ -100,12 +101,10 @@ class AiService
# generate_image_dalle(prompt)
end
private
def ask_ai(system_message, user_message)
response = @client.chat(
parameters: {
model: "gpt-4o",
model: "gpt-4o-mini",
messages:
[ {
role: "system",
@ -121,6 +120,8 @@ class AiService
response.dig("choices", 0, "message", "content")
end
private
def generate_image_prompt_request(city, weather_data, city_desc)
region = city.country.region&.name || ""
country = city.country.name || ""

View File

@ -0,0 +1,13 @@
class GenerateCityDescriptionWorker
include Sidekiq::Worker
def perform(city_id, force_refresh = false)
@city = City.find(city_id)
Rails.logger.info "Get [#{@city.name}] Description with [#{force_refresh}] force refresh."
desc = @city.get_description('en', force_refresh)
Rails.logger.info "Successful Generate #{city.name} Desc: #{desc}"
rescue StandardError => e
Rails.logger.error "Error generating desc for city #{city_id}: #{e.message}"
Rails.logger.error e.backtrace.join("\n")
end
end

View File

@ -1 +1 @@
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--d7o8roTgR6J0L8GM--7Im0i3w2N7jwl89+qBJApg==
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--mOz+PKoAApIdZVuR--7zDqyZ8Mn6kPEy64YeYNNA==

View File

@ -0,0 +1,5 @@
class AddDescriptionToCities < ActiveRecord::Migration[8.0]
def change
add_column :cities, :description_translations, :text, default: "{}", null: false
end
end

3
db/schema.rb generated
View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_02_26_011649) do
ActiveRecord::Schema[8.0].define(version: 2025_04_12_054340) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
@ -126,6 +126,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_02_26_011649) do
t.boolean "flag", default: true
t.string "wiki_data_id"
t.bigint "state_id"
t.text "description_translations", default: "{}", null: false
t.index ["country_id"], name: "index_cities_on_country_id"
t.index ["slug"], name: "index_cities_on_slug", unique: true
t.index ["state_id"], name: "index_cities_on_state_id"