today_ai_weather/app/services/ai_service.rb
songtianlun b0b64c2fe3 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.
2025-04-12 14:03:48 +08:00

156 lines
6.3 KiB
Ruby

class AiService
MODELS = {
dalle: "dall-e-3",
flux: "black-forest-labs/FLUX.1-schnell"
}.freeze
def initialize
@client = OpenAI::Client.new(
access_token: Rails.application.credentials.openai.token,
uri_base: Rails.application.credentials.openai.uri,
log_errors: Rails.env.development?, # 只在开发环境下启用
request_timeout: 240
)
# 这里假设你已经在credentials中添加了siliconflow相关配置
@flux_token = Rails.application.credentials.siliconflow.token
@flux_api_url = Rails.application.credentials.siliconflow.url
end
def generate_prompt(city, weather_data)
# 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."
user_message = generate_image_prompt_request(city, weather_data, city_desc)
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 || ""
# 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
when :flux
generate_image_flux(prompt)
when :dalle
generate_image_dalle(prompt)
else
generate_image_flux(prompt) # 默认使用FLUX
end
end
def generate_image_dalle(prompt)
response = @client.images.generate(
parameters: {
model: MODELS[:dalle],
prompt: prompt,
size: "1792x1024",
quality: "hd",
n: 1
}
)
response.dig("data", 0, "url")
end
def generate_image_flux(prompt)
uri = URI(@flux_api_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{@flux_token}"
request["Content-Type"] = "application/json"
# 生成随机种子
seed = rand(10000000000)
request.body = {
model: MODELS[:flux],
prompt: prompt,
# image_size: "1024x576",
image_size: "1792x1024",
seed: seed,
prompt_enhancement: false
}.to_json
response = http.request(request)
if response.is_a?(Net::HTTPSuccess)
result = JSON.parse(response.body)
result.dig("data", 0, "url")
else
Rails.logger.error("FLUX API error: #{response.code} - #{response.body}")
# 异常时回退到DALL-E
# generate_image_dalle(prompt)
end
rescue => e
Rails.logger.error("Error generating image with FLUX: #{e.message}")
# 异常时回退到DALL-E
# generate_image_dalle(prompt)
end
def ask_ai(system_message, user_message)
response = @client.chat(
parameters: {
model: "gpt-4o-mini",
messages:
[ {
role: "system",
content: system_message
}, {
role: "user",
content: user_message
} ],
temperature: 0.7,
max_tokens: 300
}
)
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 || ""
state = city.state&.name || ""
<<~PROMPT
Generate a highly detailed and photorealistic image prompt capturing a specific weather moment in #{city.name}, #{state}, #{country}, #{region}.
**Scene Inspiration:**
#{city_desc}
(Use this description to inform the specific elements and mood of the scene).
**Weather Conditions to Depict:**
- Temperature Feel: Convey the atmosphere associated with #{weather_data[:temperature]}°C.
- Visible Weather: #{weather_data[:description]}. Show its effects on the environment (e.g., rain slicking streets, sun casting sharp shadows, fog softening distant objects).
- Sky & Light: Sky is #{weather_data[:cloud]}% cloud covered. Render the lighting accurately for the time: #{weather_data[:time]} (e.g., warm morning light, bright midday sun, soft afternoon light, dramatic sunset colors, twilight ambiance, or night scene lighting like streetlights).
**Mandatory Requirements for Realism and Aesthetics:**
- **Style:** Photorealistic photograph, hyperrealistic, cinematic quality. Capture fine details, textures (e.g., wet pavement, brickwork, foliage), and accurate lighting.
- **Composition:** Atmospheric and artistic composition (e.g., rule of thirds, leading lines), creating depth and visual interest. Consider a natural eye-level perspective or a slightly elevated viewpoint for landscape/cityscape.
- **Landmarks/Architecture:** Feature iconic landmarks or characteristic architecture from #{city.name} as naturally integrated elements within the scene.
- **Clarity & Brightness:** Ensure the image is sharp, clear, and well-exposed. Avoid excessive blur or motion artifacts unless intentionally used for artistic effect (like long exposure for rain).
- **Lighting Enhancement:**
- Even in overcast conditions (high #{weather_data[:cloud]}), maintain visual appeal with soft, diffused light, ensuring details in shadows are visible and colors remain natural yet engaging.
- On sunny days (low #{weather_data[:cloud]}%), emphasize the brightness, warmth, contrast, and potentially lens flare for added realism.
- **Avoid:** Do NOT produce illustrations, paintings, sketches, anime, cartoons, or overly stylized digital art. The final image must look like a real photograph.
**Output Format:** Directly output the complete, detailed image generation prompt string only. No introductory text, labels, or explanations.
PROMPT
end
end