generated from google-gemini/aistudio-repository-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathApp.tsx
More file actions
152 lines (131 loc) · 5.98 KB
/
App.tsx
File metadata and controls
152 lines (131 loc) · 5.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import React, { useState, useRef, useCallback } from 'react';
import { generateEulogyText, createAudioBufferFromBase64 } from './services/geminiService';
import { SpeakerIcon, PlayIcon, StopIcon, LoadingSpinner } from './components/Icons';
type Status = 'idle' | 'loading' | 'success' | 'error';
const App: React.FC = () => {
const [status, setStatus] = useState<Status>('idle');
const [eulogyText, setEulogyText] = useState<string>('');
const [error, setError] = useState<string>('');
const [audioBuffer, setAudioBuffer] = useState<AudioBuffer | null>(null);
const [isPlaying, setIsPlaying] = useState<boolean>(false);
const audioContextRef = useRef<AudioContext | null>(null);
const audioSourceRef = useRef<AudioBufferSourceNode | null>(null);
const handleGenerate = async () => {
setStatus('loading');
setError('');
setEulogyText('');
setAudioBuffer(null);
if (isPlaying) {
handleStop();
}
try {
// 1. Generate the eulogy text
const textPrompt = 'یک مداحی در مورد یاران امام زمان بساز و بگو ظهور نزدیکه';
const generatedText = await generateEulogyText(textPrompt);
setEulogyText(generatedText);
// 2. Generate speech from the text and create an AudioBuffer
const buffer = await createAudioBufferFromBase64(generatedText);
setAudioBuffer(buffer);
setStatus('success');
} catch (err) {
console.error(err);
const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred.';
setError(`خطا در تولید محتوا: ${errorMessage}`);
setStatus('error');
}
};
const handlePlay = useCallback(() => {
if (!audioBuffer || isPlaying) return;
if (!audioContextRef.current || audioContextRef.current.state === 'closed') {
const AudioContext = window.AudioContext || (window as any).webkitAudioContext;
audioContextRef.current = new AudioContext({ sampleRate: 24000 });
}
const context = audioContextRef.current;
const source = context.createBufferSource();
source.buffer = audioBuffer;
source.connect(context.destination);
source.onended = () => {
setIsPlaying(false);
audioSourceRef.current = null;
};
source.start(0);
audioSourceRef.current = source;
setIsPlaying(true);
}, [audioBuffer, isPlaying]);
const handleStop = useCallback(() => {
if (!isPlaying || !audioSourceRef.current) return;
audioSourceRef.current.stop();
// onended will handle the state change
}, [isPlaying]);
const renderContent = () => {
switch (status) {
case 'loading':
return (
<div className="text-center text-gray-300 flex flex-col items-center gap-4">
<LoadingSpinner />
<p className="text-lg animate-pulse">در حال ساختن مداحی... این فرآیند ممکن است کمی طول بکشد.</p>
</div>
);
case 'success':
return (
<div className="w-full max-w-2xl bg-gray-800/50 backdrop-blur-sm p-6 rounded-lg shadow-xl border border-teal-500/20">
<h2 className="text-2xl font-bold text-teal-300 mb-4">مداحی آماده شد</h2>
<div className="bg-gray-900/50 p-4 rounded-md mb-6 max-h-96 overflow-y-auto">
<p className="text-lg text-gray-200 leading-loose whitespace-pre-wrap font-serif">{eulogyText}</p>
</div>
{audioBuffer && (
<div className="flex justify-center">
<button
onClick={isPlaying ? handleStop : handlePlay}
className="flex items-center gap-3 px-6 py-3 bg-teal-600 hover:bg-teal-500 text-white font-bold rounded-full transition-all duration-300 shadow-lg focus:outline-none focus:ring-2 focus:ring-teal-400 focus:ring-opacity-75"
>
{isPlaying ? <StopIcon /> : <PlayIcon />}
<span>{isPlaying ? 'توقف' : 'پخش صوت'}</span>
</button>
</div>
)}
</div>
);
case 'error':
return (
<div className="text-center text-red-400 bg-red-900/50 p-4 rounded-lg">
<p>{error}</p>
</div>
);
case 'idle':
default:
return (
<div className="text-center">
<h2 className="text-2xl md:text-3xl text-gray-300 mb-4">ای یاران، وقت یاری است</h2>
<p className="text-gray-400 mb-8 max-w-prose mx-auto">
برای ساختن یک مداحی الهامبخش در وصف یاران امام زمان (عج) و نزدیکی ظهور، بر روی دکمه زیر کلیک کنید.
</p>
<button
onClick={handleGenerate}
disabled={status === 'loading'}
className="group inline-flex items-center justify-center gap-3 px-8 py-4 bg-teal-600 text-white font-semibold rounded-lg shadow-lg hover:bg-teal-700 transition-transform transform hover:scale-105 duration-300 disabled:bg-gray-500 disabled:cursor-not-allowed"
>
<SpeakerIcon />
<span>بساز مداحی ظهور را</span>
</button>
</div>
);
}
};
return (
<main className="min-h-screen bg-gray-900 text-white flex flex-col items-center justify-center p-4 sm:p-6 font-sans">
<div className="w-full max-w-4xl flex flex-col items-center text-center">
<div className="mb-8">
<h1 className="text-4xl sm:text-5xl md:text-6xl font-extrabold tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-teal-300 to-cyan-500 mb-2">
مداحی ساز ظهور
</h1>
<p className="text-gray-400 text-lg">با قدرت هوش مصنوعی، نوایی برای منتظران</p>
</div>
<div className="w-full min-h-[300px] flex items-center justify-center">
{renderContent()}
</div>
</div>
</main>
);
};
export default App;