Skip to content

Commit b8b5b4c

Browse files
authored
Merge pull request #24 from intellwe/feat/settings
FEAT: Developed settings - API Key management, User profile settings
2 parents 20aeed0 + f63c325 commit b8b5b4c

5 files changed

Lines changed: 600 additions & 57 deletions

File tree

src/App.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ import StandaloneTextToSpeech from "./components/StandaloneTextToSpeech";
2727
import VoiceChanger from "./components/VoiceChanger";
2828
import DocumentToMedia from "./components/DocumentToMedia";
2929
import { AuthProvider, useAuth } from "./contexts/AuthContext";
30+
import { ApiKeyProvider } from "./contexts/ApiKeyContext";
3031
import AuthModal from "./components/AuthModal";
32+
import SettingsModal from "./components/SettingsModal";
3133

3234
function AppContent() {
3335
const [transcript, setTranscript] = useState<TranscriptResponse | null>(null);
@@ -42,6 +44,7 @@ function AppContent() {
4244
>("dashboard");
4345
const [sidebarOpen, setSidebarOpen] = useState(true);
4446
const [showAuthModal, setShowAuthModal] = useState(false);
47+
const [showSettingsModal, setShowSettingsModal] = useState(false);
4548

4649
const { currentUser, userProfile, logout } = useAuth();
4750

@@ -389,9 +392,14 @@ function AppContent() {
389392
Sign In
390393
</button>
391394
)}
392-
<button className="p-2 rounded-lg hover:bg-gray-700 transition-colors">
393-
<Settings className="h-5 w-5 text-gray-300" />
394-
</button>
395+
{currentUser && (
396+
<button
397+
onClick={() => setShowSettingsModal(true)}
398+
className="p-2 rounded-lg hover:bg-gray-700 transition-colors"
399+
>
400+
<Settings className="h-5 w-5 text-gray-300" />
401+
</button>
402+
)}
395403
</div>
396404
</div>
397405
</header>
@@ -547,7 +555,7 @@ function AppContent() {
547555

548556
{activeTab === "document-to-media" && (
549557
<div className="p-4 lg:p-6">
550-
<DocumentToMedia apiKey={apiKey} />
558+
<DocumentToMedia />
551559
</div>
552560
)}
553561
</div>
@@ -577,7 +585,7 @@ function AppContent() {
577585
<span className="ml-1">© {currentYear}</span>
578586
</p>
579587
<div className="text-xs text-gray-500">
580-
Professional AI Studio v3.1
588+
Professional AI Studio v3.2
581589
</div>
582590
</div>
583591
</footer>
@@ -588,14 +596,21 @@ function AppContent() {
588596
onClose={() => setShowAuthModal(false)}
589597
defaultMode="login"
590598
/>
599+
600+
<SettingsModal
601+
isOpen={showSettingsModal}
602+
onClose={() => setShowSettingsModal(false)}
603+
/>
591604
</div>
592605
);
593606
}
594607

595608
function App() {
596609
return (
597610
<AuthProvider>
598-
<AppContent />
611+
<ApiKeyProvider>
612+
<AppContent />
613+
</ApiKeyProvider>
599614
</AuthProvider>
600615
);
601616
}

src/components/DocumentToMedia.tsx

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@ import axios from "axios";
1919
import pdfToText from "react-pdftotext";
2020
import OpenAI from "openai";
2121
import { useAuth } from "../contexts/AuthContext";
22+
import { useApiKeys } from "../contexts/ApiKeyContext";
2223

23-
interface DocumentToMediaProps {
24-
apiKey: string;
25-
}
24+
interface DocumentToMediaProps {}
2625

2726
interface Voice {
2827
voice_id: string;
@@ -31,7 +30,7 @@ interface Voice {
3130
preview_url?: string;
3231
}
3332

34-
const DocumentToMedia: React.FC<DocumentToMediaProps> = ({ apiKey }) => {
33+
const DocumentToMedia: React.FC<DocumentToMediaProps> = () => {
3534
const [pdfFile, setPdfFile] = useState<File | null>(null);
3635
const [extractedText, setExtractedText] = useState<string>("");
3736
const [customPrompt, setCustomPrompt] = useState<string>("");
@@ -54,7 +53,6 @@ const DocumentToMedia: React.FC<DocumentToMediaProps> = ({ apiKey }) => {
5453
const [stability, setStability] = useState(0.5);
5554
const [similarityBoost, setSimilarityBoost] = useState(0.75);
5655
const [isLoadingVoices, setIsLoadingVoices] = useState(false);
57-
const [openaiApiKey, setOpenaiApiKey] = useState<string>("");
5856
const [useSimpleProcessing, setUseSimpleProcessing] = useState<boolean>(true);
5957

6058
const fileInputRef = useRef<HTMLInputElement>(null);
@@ -63,20 +61,24 @@ const DocumentToMedia: React.FC<DocumentToMediaProps> = ({ apiKey }) => {
6361
const canvasRef = useRef<HTMLCanvasElement | null>(null);
6462

6563
const { updateUserStats } = useAuth();
64+
const { getElevenLabsKey, getOpenAiKey } = useApiKeys();
6665

6766
// Fetch ElevenLabs voices on component mount
6867
useEffect(() => {
69-
if (apiKey) {
68+
const elevenLabsKey = getElevenLabsKey();
69+
if (elevenLabsKey) {
7070
fetchVoices();
7171
}
72-
}, [apiKey]);
72+
}, [getElevenLabsKey]);
7373

7474
const fetchVoices = async () => {
7575
setIsLoadingVoices(true);
76+
const elevenLabsKey = getElevenLabsKey();
77+
7678
try {
7779
const response = await axios.get("https://api.elevenlabs.io/v1/voices", {
7880
headers: {
79-
"xi-api-key": apiKey,
81+
"xi-api-key": elevenLabsKey,
8082
},
8183
});
8284

@@ -239,10 +241,10 @@ const DocumentToMedia: React.FC<DocumentToMediaProps> = ({ apiKey }) => {
239241
try {
240242
let result = extractedText;
241243

242-
if (!useSimpleProcessing && openaiApiKey) {
244+
if (!useSimpleProcessing && getOpenAiKey()) {
243245
try {
244246
const openai = new OpenAI({
245-
apiKey: openaiApiKey,
247+
apiKey: getOpenAiKey(),
246248
dangerouslyAllowBrowser: true,
247249
});
248250

@@ -301,7 +303,7 @@ const DocumentToMedia: React.FC<DocumentToMediaProps> = ({ apiKey }) => {
301303
};
302304

303305
const generateAudio = async () => {
304-
if (!processedText || !apiKey) return;
306+
if (!processedText || !getElevenLabsKey()) return;
305307

306308
setIsGeneratingAudio(true);
307309
setError(null);
@@ -321,7 +323,7 @@ const DocumentToMedia: React.FC<DocumentToMediaProps> = ({ apiKey }) => {
321323
headers: {
322324
Accept: "audio/mpeg",
323325
"Content-Type": "application/json",
324-
"xi-api-key": apiKey,
326+
"xi-api-key": getElevenLabsKey(),
325327
},
326328
responseType: "blob",
327329
}
@@ -729,21 +731,23 @@ const DocumentToMedia: React.FC<DocumentToMediaProps> = ({ apiKey }) => {
729731
</div>
730732

731733
{!useSimpleProcessing && (
732-
<div className="space-y-2">
733-
<label className="block text-sm font-medium text-gray-300">
734-
OpenAI API Key (for AI processing)
735-
</label>
736-
<input
737-
type="password"
738-
value={openaiApiKey}
739-
onChange={(e) => setOpenaiApiKey(e.target.value)}
740-
placeholder="sk-..."
741-
className="w-full px-4 py-3 bg-gray-600 border border-gray-500 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:border-transparent transition-all duration-200"
742-
/>
743-
<p className="text-xs text-gray-500">
744-
Required for advanced AI features like translation to Hindi,
745-
content summarization, etc.
746-
</p>
734+
<div className="mb-6">
735+
<div className="bg-cyan-900/20 border border-cyan-500/30 rounded-lg p-4">
736+
<p className="text-cyan-400 text-sm font-medium mb-2">
737+
🔑 AI Processing Enabled
738+
</p>
739+
<p className="text-gray-400 text-xs">
740+
{getOpenAiKey()
741+
? "Using your OpenAI API key for enhanced AI features like translation, summarization, and content enhancement."
742+
: "OpenAI API key not found. Please add your OpenAI API key in Settings to enable AI processing features."}
743+
</p>
744+
{!getOpenAiKey() && (
745+
<p className="text-yellow-400 text-xs mt-2">
746+
⚠️ Without an OpenAI API key, only simple text processing
747+
will be available.
748+
</p>
749+
)}
750+
</div>
747751
</div>
748752
)}
749753
</div>
@@ -766,7 +770,7 @@ const DocumentToMedia: React.FC<DocumentToMediaProps> = ({ apiKey }) => {
766770
disabled={
767771
isProcessing ||
768772
!customPrompt.trim() ||
769-
(!useSimpleProcessing && !openaiApiKey.trim())
773+
(!useSimpleProcessing && !getOpenAiKey())
770774
}
771775
className="w-full bg-cyan-600 hover:bg-cyan-700 text-white font-medium px-6 py-3 rounded-lg transition-all duration-200 flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed shadow-lg hover:shadow-xl"
772776
>
@@ -865,10 +869,9 @@ const DocumentToMedia: React.FC<DocumentToMediaProps> = ({ apiKey }) => {
865869

866870
{processedText && (
867871
<div className="bg-gray-800 rounded-xl p-4 lg:p-6 border border-gray-700">
868-
<div className="flex flex-col sm:flex-row sm:items-center justify-between mb-2 space-y-2 sm:space-y-0">
869-
<h3 className="text-lg lg:text-xl font-bold text-white flex items-center">
870-
<Mic className="mr-2 h-5 w-5 text-green-400" />
871-
Professional Audio Generation
872+
<div className="flex items-center justify-between">
873+
<h3 className="text-lg font-semibold text-white">
874+
Audio Generation
872875
</h3>
873876
<div className="flex items-center space-x-2">
874877
<button
@@ -894,6 +897,15 @@ const DocumentToMedia: React.FC<DocumentToMediaProps> = ({ apiKey }) => {
894897
Generate high-quality, natural-sounding audio narration from your
895898
processed content
896899
</p>
900+
{audioUrl && (
901+
<audio
902+
ref={audioRef}
903+
src={audioUrl}
904+
onEnded={() => setIsPlaying(false)}
905+
onPause={() => setIsPlaying(false)}
906+
onPlay={() => setIsPlaying(true)}
907+
/>
908+
)}
897909

898910
<div className="mb-4 space-y-2">
899911
<label className="block text-sm font-medium text-gray-300">
@@ -918,12 +930,12 @@ const DocumentToMedia: React.FC<DocumentToMediaProps> = ({ apiKey }) => {
918930
)}
919931
</select>
920932

921-
{selectedVoice && (
933+
{/* {selectedVoice && (
922934
<div className="mt-2 p-2 bg-gray-700 rounded text-sm border border-gray-600">
923935
<span className="text-gray-400">Selected Voice ID: </span>
924936
<span className="text-cyan-400 font-mono">{selectedVoice}</span>
925937
</div>
926-
)}
938+
)} */}
927939
</div>
928940

929941
{showSettings && (
@@ -966,7 +978,7 @@ const DocumentToMedia: React.FC<DocumentToMediaProps> = ({ apiKey }) => {
966978
<button
967979
onClick={generateAudio}
968980
disabled={isGeneratingAudio || !processedText.trim()}
969-
className="w-full bg-green-600 hover:bg-green-700 text-white font-medium px-6 py-3 rounded-lg transition-all duration-200 flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed shadow-lg hover:shadow-xl mb-4"
981+
className="w-full bg-cyan-600 hover:bg-cyan-700 text-white font-medium px-6 py-3 rounded-lg transition-all duration-200 flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed shadow-lg hover:shadow-xl mb-4"
970982
>
971983
{isGeneratingAudio ? (
972984
<>
@@ -998,7 +1010,7 @@ const DocumentToMedia: React.FC<DocumentToMediaProps> = ({ apiKey }) => {
9981010

9991011
<button
10001012
onClick={downloadAudio}
1001-
className="flex items-center space-x-2 bg-green-600 text-white px-6 py-3 rounded-lg hover:bg-green-700 transition-colors w-full sm:w-auto justify-center"
1013+
className="flex items-center space-x-2 bg-cyan-600 text-white px-6 py-3 rounded-lg hover:bg-cyan-700 transition-colors w-full sm:w-auto justify-center"
10021014
>
10031015
<Download className="h-5 w-5" />
10041016
<span>Download Audio</span>
@@ -1068,7 +1080,7 @@ const DocumentToMedia: React.FC<DocumentToMediaProps> = ({ apiKey }) => {
10681080
<div className="flex justify-center">
10691081
<button
10701082
onClick={downloadVideo}
1071-
className="flex items-center space-x-2 bg-green-600 text-white px-6 py-3 rounded-lg hover:bg-green-700 transition-colors"
1083+
className="flex items-center space-x-2 bg-cyan-600 text-white px-6 py-3 rounded-lg hover:bg-cyan-700 transition-colors"
10721084
>
10731085
<Download className="h-5 w-5" />
10741086
<span>Download Video</span>

0 commit comments

Comments
 (0)