부모 티켓
2개 티켓

백로그

0
티켓 없음

할 일

0
티켓 없음

진행 중

0
티켓 없음

리뷰

0
티켓 없음

완료 (30일)

2
높음 687b92f9
서브 티켓 [P3] AI 콘텐츠 자동 생성

AI QT 콘텐츠 자동 생성 - 서비스 + Job + Admin 컨트롤러/뷰 + 테스트

## 목표 Admin에서 QT 테마를 선택하고 AI 생성 요청 시 Solid Queue Job으로 콘텐츠를 자동 생성하는 전체 플로우 구현 ## 스키마 현황 (마이그레이션 불필요!) - QtTheme: is_ai_generated, ai_prompt, bible_books, generation_status(enum: draft/generating/completed/published) 컬럼 이미 존재 - QtContent: day_number, bible_passage, reading_passage, theme_title, content, questions(json), difficulty 등 컬럼 존재 ## 구현 항목 ### 1. AiQtGenerator 서비스 - 파일: `app/services/ai_qt_generator.rb` - 기존 서비스 패턴 (AiMeditationAnalyzer, AiSermonInterpreter)을 따를 것 - 핵심: ```ruby class AiQtGenerator def initialize(theme) @theme = theme end def call # 1. 테마 정보로 프롬프트 구성 # 2. OpenAI::Client로 API 호출 # 3. 응답 JSON 파싱 → QtContent 레코드 생성 # 4. 결과 반환 end private def build_prompt # 한국어 QT 콘텐츠 생성 프롬프트 # 입력: theme.title, theme.description, theme.total_day, theme.bible_books # 출력 형식: JSON 배열 [{day_number, bible_passage, reading_passage, theme_title, content, questions: [...5개], difficulty, estimated_minutes}] end def api_key ENV["OPENAI_API_KEY"] || ENV["GEMINI_API_KEY"] || raise("AI API 키가 설정되지 않았습니다") end def ai_model ENV["OPENAI_API_KEY"] ? "gpt-4o-mini" : "gemini-2.0-flash" end end ``` - **프롬프트 가이드**: 레거시에서 참고. 성경 통독 QT 콘텐츠 생성용: - total_day 일분의 콘텐츠를 JSON 배열로 생성 - 각 콘텐츠: bible_passage(통독 범위), reading_passage(핵심 구절), theme_title, content(본문 해석), questions(5개 묵상 질문 배열) - difficulty 1-5, estimated_minutes 기본 15 - 한국어로 생성 - 개역개정 성경 기준 ### 2. GenerateQtContentsJob - 파일: `app/jobs/generate_qt_contents_job.rb` - Solid Queue 백그라운드 처리: ```ruby class GenerateQtContentsJob < ApplicationJob queue_as :default def perform(theme_id) theme = QtTheme.find(theme_id) theme.generating! # 상태 변경 result = AiQtGenerator.new(theme).call if result[:success] theme.completed! else theme.draft! # 실패 시 롤백 Rails.logger.error("QT generation failed for theme #{theme_id}: #{result[:error]}") end rescue => e theme&.draft! Rails.logger.error("QT generation job error: #{e.message}") raise # Solid Queue가 재시도하도록 end end ``` ### 3. Admin::QtThemesController - 파일: `app/controllers/admin/qt_themes_controller.rb` - Admin::BaseController 상속 (이미 authorize_admin! 포함) - 액션: index, show, generate ```ruby module Admin class QtThemesController < BaseController def index @themes = QtTheme.order(created_at: :desc) end def show @theme = QtTheme.find(params[:id]) @contents = @theme.qt_contents.order(:day_number) end def generate @theme = QtTheme.find(params[:id]) if @theme.generating? redirect_to admin_qt_theme_path(@theme), alert: "이미 생성 중입니다." return end # 기존 콘텐츠 삭제 후 재생성 @theme.qt_contents.destroy_all if @theme.qt_contents.any? GenerateQtContentsJob.perform_later(@theme.id) redirect_to admin_qt_theme_path(@theme), notice: "AI 콘텐츠 생성이 시작되었습니다." end end end ``` ### 4. 라우트 수정 - 파일: `config/routes.rb` - 기존 admin namespace에 추가: ```ruby namespace :admin do root "dashboard#index" resources :qt_themes, only: [:index, :show] do member do post :generate end end end ``` - **중요**: 기존 routes.rb를 먼저 읽고, admin namespace 안에 추가 ### 5. Admin 뷰 - 파일: `app/views/admin/qt_themes/index.html.erb` (신규) - 테마 목록 테이블 (제목, 일수, 상태 뱃지, AI 여부, 생성 버튼) - shared/_table, _badge 파셜 활용 - admin layout 사용 확인 - 파일: `app/views/admin/qt_themes/show.html.erb` (신규) - 테마 상세 정보 - 콘텐츠 목록 (day_number, bible_passage, theme_title) - generation_status에 따른 UI 분기: - draft: "AI 생성" 버튼 표시 - generating: "생성 중..." 로딩 표시 - completed: "생성 완료" 뱃지 + 콘텐츠 목록 - published: "발행됨" 뱃지 - "AI 생성" 버튼: `button_to generate_admin_qt_theme_path(@theme), method: :post` ### 6. 테스트 - 파일: `test/services/ai_qt_generator_test.rb` (신규) - API 호출 stub - 정상 생성 테스트 - 에러 처리 테스트 - 파일: `test/jobs/generate_qt_contents_job_test.rb` (신규) - job enqueue 테스트 - 상태 전환 테스트 - 파일: `test/controllers/admin/qt_themes_controller_test.rb` (신규) - admin 사용자 index 접근 - admin 사용자 show 접근 - admin 사용자 generate 요청 - 일반 사용자 접근 차단 - 비인증 사용자 접근 차단 ## 기존 코드 참고 (반드시 먼저 읽을 것) - `app/services/ai_meditation_analyzer.rb` - AI 서비스 패턴 - `app/services/ai_sermon_interpreter.rb` - AI 서비스 패턴 - `app/controllers/admin/base_controller.rb` - Admin 인증 - `app/controllers/admin/dashboard_controller.rb` - Admin 컨트롤러 예시 - `app/models/qt_theme.rb` - enum, 관계 - `app/models/qt_content.rb` - validates, 컬럼 - `app/views/admin/` - 기존 admin 레이아웃/뷰 구조 - `test/fixtures/qt_themes.yml` - fixture 구조 - `test/fixtures/qt_contents.yml` - fixture 구조 - `config/routes.rb` - 현재 라우트 구조 ## 주의사항 - shared 파셜의 strict locals 준수 (반드시 파셜 파일을 읽어서 필요한 locals 확인) - ERB 멀티라인 주석 안에 ERB 태그 금지 (SystemStackError) - UUID PK 사용 - parallelize(workers: 1) 테스트 설정 확인 - 기존 테스트가 깨지지 않게 주의 - admin layout이 있는지 확인: `app/views/layouts/admin.html.erb` - SQLite JSON 컬럼은 자동 파싱 안 됨 → JSON.parse() 사용 필요할 수 있음 - 전체 테스트 통과 확인: bin/rails test

A
ai-gen-dev
9 days
높음 4681034b
서브 티켓 [P3] AI 콘텐츠 자동 생성

AI 묵상 정리 (organize_meditation) - 서비스 + 컨트롤러 + UI + 테스트

## 목표 음성인식으로 입력된 묵상 텍스트를 AI가 정리해주는 기능 구현 (Q23) ## 배경 - 사용자가 음성인식으로 묵상 내용을 입력하면 오타/문법 오류가 많음 - AI가 원본 말투를 유지하면서 오타/문법만 수정하고 반복을 제거 - 가벼운 작업이므로 동기 처리 (백그라운드 Job 불필요) ## 구현 항목 ### 1. AiMeditationOrganizer 서비스 - 파일: `app/services/ai_meditation_organizer.rb` - 기존 서비스 패턴 (AiMeditationAnalyzer, AiSermonInterpreter)을 따를 것 - 핵심: ```ruby class AiMeditationOrganizer def initialize(text, passage_reference: nil) @text = text @passage_reference = passage_reference end def call return { success: false, error: "정리할 텍스트가 없습니다." } if @text.blank? client = OpenAI::Client.new(access_token: api_key) response = client.chat(parameters: { model: ai_model, messages: [{ role: "user", content: build_prompt }], temperature: 0.3 # 낮은 temperature - 내용 보존 우선 }) organized = response.dig("choices", 0, "message", "content") { success: true, organized_text: organized&.strip } rescue => e { success: false, error: e.message } end private def build_prompt # 레거시 프롬프트 기반: # - 원본 말투 유지 # - 오타/문법만 수정 # - 반복 제거 # - 내용 추가 금지 # - passage_reference가 있으면 성경 구절 맥락 제공 end def api_key ENV["OPENAI_API_KEY"] || ENV["GEMINI_API_KEY"] || raise("AI API 키가 설정되지 않았습니다") end def ai_model ENV["OPENAI_API_KEY"] ? "gpt-4o-mini" : "gemini-2.0-flash" end end ``` ### 2. MeditationsController에 organize 액션 추가 - 파일: `app/controllers/qt/meditations_controller.rb` (또는 해당 컨트롤러) - **먼저** 기존 묵상 컨트롤러를 찾아서 읽을 것 (qt/ namespace 확인) - organize 액션 추가: ```ruby def organize @meditation = current_user.user_meditations.find(params[:id]) result = AiMeditationOrganizer.new( @meditation.personal_meditation, passage_reference: @meditation.qt_content&.reading_passage ).call if result[:success] @meditation.update(personal_meditation: result[:organized_text]) respond_to do |format| format.turbo_stream { render turbo_stream: turbo_stream.replace( "meditation-content", partial: "qt/meditations/meditation_content", locals: { meditation: @meditation } )} format.html { redirect_to qt_meditation_path(@meditation), notice: "묵상이 정리되었습니다." } end else respond_to do |format| format.turbo_stream { render turbo_stream: turbo_stream.replace( "organize-error", html: "<p class='text-red-500 text-sm mt-2'>#{result[:error]}</p>" )} format.html { redirect_to qt_meditation_path(@meditation), alert: result[:error] } end end end ``` ### 3. 라우트 수정 - 파일: `config/routes.rb` - 기존 묵상 리소스에 member 라우트 추가: ```ruby # meditations 리소스 안에: member do post :organize end ``` - **중요**: 기존 routes.rb를 먼저 읽고, 묵상 관련 라우트 구조를 파악한 후 추가 ### 4. UI - "정리하기" 버튼 - **먼저** 묵상 관련 뷰 파일을 탐색하여 어디에 버튼을 추가할지 결정: - `app/views/qt/meditations/` 디렉토리 확인 - 묵상 show 페이지 또는 form에서 personal_meditation 필드 근처 - 버튼 디자인: - personal_meditation이 있을 때만 표시 - `button_to "AI 정리", organize_qt_meditation_path(@meditation), method: :post` - 또는 Turbo 방식: data-turbo-method="post" - **Turbo Stream** 응답을 위한 파셜 필요: - `qt/meditations/_meditation_content.html.erb` (정리된 내용 표시) ### 5. 테스트 - 파일: `test/services/ai_meditation_organizer_test.rb` (신규) - 정상 정리 테스트 (API stub) - 빈 텍스트 에러 처리 - API 실패 에러 처리 - 파일: 기존 묵상 컨트롤러 테스트에 추가 - organize 인증 필수 - organize 성공 시 텍스트 업데이트 - organize 실패 시 에러 처리 - 타인 묵상 접근 불가 ## 기존 코드 참고 (반드시 먼저 읽을 것) - `app/services/ai_sermon_interpreter.rb` - AI 서비스 패턴 + Turbo Stream 응답 패턴 - `app/controllers/sermons_controller.rb` - interpret 액션 (동일 패턴) - `app/controllers/qt/meditations_controller.rb` 또는 유사 컨트롤러 - 묵상 CRUD - 묵상 관련 뷰 파일들 (qt/ 아래) - `app/models/user_meditation.rb` - 모델 구조 - `test/fixtures/user_meditations.yml` - fixture ## 주의사항 - **반드시** 기존 묵상 컨트롤러와 뷰를 먼저 읽고, 그 패턴에 맞게 구현 - AI 서비스 패턴: ruby-openai gem, OpenAI::Client.new(access_token: api_key) - temperature 0.3 (낮음) - 원본 내용 보존 우선 - shared 파셜의 strict locals 준수 - ERB 멀티라인 주석 안에 ERB 태그 금지 (SystemStackError) - UUID PK 사용 - parallelize(workers: 1) 테스트 설정 확인 - 기존 테스트가 깨지지 않게 주의 - 전체 테스트 통과 확인: bin/rails test - config/routes.rb 수정 시 기존 구조를 깨지 않게 주의

A
ai-organize-dev
9 days