refactor: [rd] CM송 → 나레이션 명칭 변경 + 결과 자동 스크롤

- 모든 UI 텍스트 CM송 → 나레이션으로 변경
- 버튼: 나레이션 제작
- 제작 시 결과 패널로 자동 스크롤
- 프롬프트, 다운로드 파일명, 저장 메시지 모두 변경
This commit is contained in:
김보곤
2026-03-05 14:51:09 +09:00
parent 446d0ff60b
commit 3bba48e443
5 changed files with 46 additions and 42 deletions

View File

@@ -1,16 +1,16 @@
@extends('layouts.app')
@section('title', 'AI CM송 제작')
@section('title', 'AI 나레이션 제작')
@section('content')
<!-- 페이지 헤더 -->
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800 flex items-center gap-2">
<i class="ri-music-2-line text-indigo-600"></i>
AI CM송 제작
AI 나레이션 제작
</h1>
<a href="{{ route('rd.cm-song.index') }}" class="bg-white hover:bg-gray-100 text-gray-700 px-4 py-2 rounded-lg border transition">
<i class="ri-arrow-left-line mr-1"></i> CM송 목록
<i class="ri-arrow-left-line mr-1"></i> 나레이션 목록
</a>
</div>
@@ -20,7 +20,7 @@
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-5 flex items-center gap-2">
<i class="ri-edit-line text-indigo-500"></i>
CM송 정보 입력
나레이션 정보 입력
</h2>
<div class="space-y-5">
@@ -76,11 +76,11 @@ class="w-full px-4 py-3 rounded-lg border border-gray-200 focus:ring-2 focus:rin
</div>
</div>
<!-- CM송 길이 슬라이더 -->
<!-- 나레이션 길이 슬라이더 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5 flex items-center gap-1.5">
<i class="ri-time-line text-gray-400"></i>
CM송 길이
나레이션 길이
</label>
<div class="flex items-center gap-4">
<input
@@ -108,29 +108,29 @@ class="flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-i
class="w-full py-3.5 bg-indigo-600 hover:bg-indigo-700 disabled:bg-gray-300 disabled:cursor-not-allowed text-white rounded-lg font-medium flex items-center justify-center gap-2 transition shadow-sm hover:shadow-md"
>
<i class="ri-sparkling-line"></i>
CM송 만들기
나레이션 제작
</button>
</div>
</div>
<!-- 결과 섹션 -->
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 flex flex-col">
<div id="resultPanel" class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 flex flex-col">
<h2 class="text-lg font-semibold text-gray-800 mb-5 flex items-center gap-2">
<i class="ri-music-2-line text-indigo-500"></i>
생성된 CM송
생성된 나레이션
</h2>
<div class="flex-1 flex flex-col justify-center" id="resultArea">
<!-- 초기 상태 -->
<div id="emptyState" class="text-center text-gray-400 py-12">
<i class="ri-music-2-line text-5xl mb-3 block opacity-20"></i>
<p class="text-sm">정보를 입력하고 버튼을 누르면<br>이곳에 CM송 나타납니다.</p>
<p class="text-sm">정보를 입력하고 버튼을 누르면<br>이곳에 나레이션 나타납니다.</p>
</div>
<!-- 로딩 상태 -->
<div id="loadingState" class="text-center text-indigo-400 py-12 hidden">
<i class="ri-loader-4-line text-5xl mb-3 block animate-spin"></i>
<p class="text-sm" id="loadingText">AI가 멋진 가사를 있어요...</p>
<p class="text-sm" id="loadingText">AI가 나레이션을 작성하 있어요...</p>
</div>
<!-- 결과 -->
@@ -199,6 +199,7 @@ class="flex items-center gap-1.5 px-5 py-2 bg-white hover:bg-gray-50 text-gray-6
const moodSelector = document.getElementById('moodSelector');
const durationSlider = document.getElementById('durationSlider');
const durationDisplay = document.getElementById('durationDisplay');
const resultPanel = document.getElementById('resultPanel');
const emptyState = document.getElementById('emptyState');
const loadingState = document.getElementById('loadingState');
const loadingText = document.getElementById('loadingText');
@@ -290,7 +291,7 @@ function showState(state) {
if (state === 'result') resultState.classList.remove('hidden');
}
// CM송 생성
// 나레이션 생성
async function generate() {
const companyName = companyInput.value.trim();
const industry = industryInput.value.trim();
@@ -302,14 +303,17 @@ function showState(state) {
}
generateBtn.disabled = true;
generateBtn.innerHTML = '<i class="ri-loader-4-line animate-spin"></i> CM송 제작 중...';
generateBtn.innerHTML = '<i class="ri-loader-4-line animate-spin"></i> 나레이션 제작 중...';
showState('loading');
loadingText.textContent = 'AI가 멋진 가사를 쓰고 있어요...';
loadingText.textContent = 'AI가 나레이션을 작성하고 있어요...';
audioSection.classList.add('hidden');
currentAudioData = null;
currentAudioMimeType = null;
currentAudioBlob = null;
// 결과 패널로 스크롤
resultPanel.scrollIntoView({ behavior: 'smooth', block: 'start' });
try {
// 1. 가사 생성
const lyricsRes = await fetch('{{ route("rd.cm-song.generate-lyrics") }}', {
@@ -328,7 +332,7 @@ function showState(state) {
const lyricsData = await lyricsRes.json();
if (!lyricsData.success) {
throw new Error(lyricsData.error || '가사 생성 실패');
throw new Error(lyricsData.error || '나레이션 생성 실패');
}
currentLyrics = lyricsData.lyrics;
@@ -371,23 +375,23 @@ function showState(state) {
}
} catch (error) {
console.error('CM송 생성 오류:', error);
console.error('나레이션 생성 오류:', error);
alert('생성 중 오류가 발생했습니다: ' + error.message);
showState('empty');
} finally {
generateBtn.disabled = false;
generateBtn.innerHTML = '<i class="ri-sparkling-line"></i> CM송 만들기';
generateBtn.innerHTML = '<i class="ri-sparkling-line"></i> 나레이션 제작';
}
}
// 다운로드
downloadBtn.addEventListener('click', function () {
if (!currentAudioBlob) return;
const companyName = companyInput.value.trim() || 'CM송';
const companyName = companyInput.value.trim() || '나레이션';
const url = URL.createObjectURL(currentAudioBlob);
const a = document.createElement('a');
a.href = url;
a.download = 'CM송_' + companyName + '_' + new Date().toISOString().slice(0,10) + '.wav';
a.download = '나레이션_' + companyName + '_' + new Date().toISOString().slice(0,10) + '.wav';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
@@ -421,7 +425,7 @@ function showState(state) {
const data = await res.json();
if (data.success) {
alert('CM송이 저장되었습니다.');
alert('나레이션이 저장되었습니다.');
window.location.href = '{{ route("rd.cm-song.index") }}';
} else {
throw new Error(data.error || '저장 실패');

View File

@@ -1,20 +1,20 @@
@extends('layouts.app')
@section('title', 'CM송 관리')
@section('title', '나레이션 관리')
@section('content')
<!-- 페이지 헤더 -->
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800 flex items-center gap-2">
<i class="ri-music-2-line text-indigo-600"></i>
CM송 관리
나레이션 관리
</h1>
<div class="flex gap-2">
<a href="{{ route('rd.index') }}" class="bg-white hover:bg-gray-100 text-gray-700 px-4 py-2 rounded-lg border transition">
<i class="ri-arrow-left-line mr-1"></i> R&D
</a>
<a href="{{ route('rd.cm-song.create') }}" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg transition">
<i class="ri-add-line mr-1"></i> CM송 제작
<i class="ri-add-line mr-1"></i> 나레이션 제작
</a>
</div>
</div>
@@ -91,9 +91,9 @@ class="p-1.5 text-gray-400 hover:text-red-600 transition"
@else
<div class="py-16 text-center text-gray-400">
<i class="ri-music-2-line text-5xl mb-3 block opacity-20"></i>
<p>아직 생성된 CM송 없습니다.</p>
<p>아직 생성된 나레이션 없습니다.</p>
<a href="{{ route('rd.cm-song.create') }}" class="text-indigo-600 hover:text-indigo-800 text-sm mt-2 inline-block">
번째 CM송 만들어보세요
번째 나레이션 만들어보세요
</a>
</div>
@endif
@@ -103,7 +103,7 @@ class="p-1.5 text-gray-400 hover:text-red-600 transition"
@push('scripts')
<script>
function deleteSong(id) {
if (!confirm('이 CM송을 삭제하시겠습니까?')) return;
if (!confirm('이 나레이션을 삭제하시겠습니까?')) return;
fetch(`/rd/cm-song/${id}`, {
method: 'DELETE',
headers: {

View File

@@ -1,13 +1,13 @@
@extends('layouts.app')
@section('title', 'CM송 상세 - ' . $song->company_name)
@section('title', '나레이션 상세 - ' . $song->company_name)
@section('content')
<!-- 페이지 헤더 -->
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800 flex items-center gap-2">
<i class="ri-music-2-line text-indigo-600"></i>
CM송 상세
나레이션 상세
</h1>
<div class="flex gap-2">
<a href="{{ route('rd.cm-song.index') }}" class="bg-white hover:bg-gray-100 text-gray-700 px-4 py-2 rounded-lg border transition">
@@ -24,7 +24,7 @@
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-5 flex items-center gap-2">
<i class="ri-information-line text-indigo-500"></i>
CM송 정보
나레이션 정보
</h2>
<div class="space-y-4">

View File

@@ -94,15 +94,15 @@
</div>
</a>
<!-- AI CM송 제작 -->
<!-- AI 나레이션 제작 -->
<a href="{{ route('rd.cm-song.index') }}" class="bg-white rounded-lg shadow-sm p-6 hover:shadow-md transition group">
<div class="flex items-start gap-4">
<div class="w-14 h-14 bg-gradient-to-br from-indigo-500 to-violet-600 rounded-xl flex items-center justify-center text-white shrink-0">
<i class="ri-music-2-line text-2xl"></i>
</div>
<div>
<h3 class="text-lg font-semibold text-gray-800 group-hover:text-indigo-600 transition">AI CM송 제작</h3>
<p class="text-sm text-gray-500 mt-1">회사명과 업종을 입력하면 AI가 CM송 가사를 작성하고 목소리를 입혀줍니다.</p>
<h3 class="text-lg font-semibold text-gray-800 group-hover:text-indigo-600 transition">AI 나레이션 제작</h3>
<p class="text-sm text-gray-500 mt-1">회사명과 업종을 입력하면 AI가 나레이션을 작성하고 목소리를 입혀줍니다.</p>
<div class="flex gap-2 mt-3">
<span class="px-2 py-0.5 bg-purple-50 text-purple-600 text-xs rounded-full">Gemini TTS</span>
<span class="px-2 py-0.5 bg-indigo-50 text-indigo-600 text-xs rounded-full">음성 생성</span>