Spaces:
Paused
Paused
Download frontend/src/components/pages/SettingsModal.tsx from SkillSync-II/skillsync-cli: direct link, hf CLI and curl.
- Browser
- Download file 9.66 kB
-
https://huggingface.co/spaces/SkillSync-II/skillsync-cli/resolve/main/frontend/src/components/pages/SettingsModal.tsx
- Command line
-
hf download hf://spaces/SkillSync-II/skillsync-cli/frontend/src/components/pages/SettingsModal.tsx
-
curl -L -o SettingsModal.tsx https://huggingface.co/spaces/SkillSync-II/skillsync-cli/resolve/main/frontend/src/components/pages/SettingsModal.tsx
9.66 kB
| import React from 'react'; | |
| import { motion } from 'framer-motion'; | |
| import { ToggleLeft, ToggleRight, Zap, Check } from 'lucide-react'; | |
| import { Card, Button, Select } from '@/components/common'; | |
| import Modal from '@/components/common/Modal'; | |
| import { useJobStore } from '@/store/appStore'; | |
| import { jobsAPI } from '@/utils/api'; | |
| import type { TailoringMode, LLMProvider } from '@/types'; | |
| interface SettingsModalProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| } | |
| const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose }) => { | |
| const { | |
| tailoringMode, | |
| llmProvider, | |
| setEnableProfessionalSummary: setStoreProfessionalSummary, | |
| setIncludeCoverLetters: setStoreCoverLetters, | |
| setTailoringMode, | |
| setLLMProvider, | |
| setNotification, | |
| } = useJobStore(); | |
| const [draftMode, setDraftMode] = React.useState<TailoringMode>(tailoringMode); | |
| const [draftProvider, setDraftProvider] = React.useState<LLMProvider>(llmProvider || 'groq'); | |
| const [enableProfessionalSummary, setEnableProfessionalSummary] = React.useState(false); | |
| const [includeCoverLetters, setIncludeCoverLetters] = React.useState(false); | |
| const [isSaving, setIsSaving] = React.useState(false); | |
| const [isLoading, setIsLoading] = React.useState(false); | |
| const llmProviders: Array<{ value: LLMProvider; label: string; description: string }> = [ | |
| { value: 'ollama', label: 'Ollama (Local)', description: 'Runs locally, slowest' }, | |
| { value: 'groq', label: 'Groq API', description: 'Fast and cost-effective' }, | |
| { value: 'grok', label: 'Grok API', description: 'Fast and affordable' }, | |
| { value: 'openai', label: 'OpenAI', description: 'Premium quality' }, | |
| ]; | |
| React.useEffect(() => { | |
| if (!isOpen) { | |
| return; | |
| } | |
| setDraftMode(tailoringMode); | |
| setDraftProvider(llmProvider || 'groq'); | |
| const loadConfig = async () => { | |
| setIsLoading(true); | |
| try { | |
| const config = await jobsAPI.getConfig(); | |
| setDraftMode(config.tailoring_mode); | |
| setDraftProvider(config.llm_provider); | |
| setEnableProfessionalSummary(!!config.enable_professional_summary); | |
| setIncludeCoverLetters(!!config.include_cover_letters); | |
| setTailoringMode(config.tailoring_mode); | |
| setLLMProvider(config.llm_provider); | |
| setStoreProfessionalSummary(!!config.enable_professional_summary); | |
| setStoreCoverLetters(!!config.include_cover_letters); | |
| } catch (error) { | |
| setNotification({ | |
| type: 'error', | |
| message: 'Could not load settings. Using current values.', | |
| }); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| void loadConfig(); | |
| }, [ | |
| isOpen, | |
| llmProvider, | |
| setLLMProvider, | |
| setNotification, | |
| setStoreCoverLetters, | |
| setStoreProfessionalSummary, | |
| setTailoringMode, | |
| tailoringMode, | |
| ]); | |
| const handleSave = async () => { | |
| setIsSaving(true); | |
| try { | |
| await jobsAPI.saveSettings({ | |
| tailoring_mode: draftMode, | |
| llm_provider: draftProvider, | |
| enable_professional_summary: enableProfessionalSummary, | |
| include_cover_letters: includeCoverLetters, | |
| }); | |
| setTailoringMode(draftMode); | |
| setLLMProvider(draftProvider); | |
| setStoreProfessionalSummary(enableProfessionalSummary); | |
| setStoreCoverLetters(includeCoverLetters); | |
| setNotification({ | |
| type: 'success', | |
| message: 'Settings saved successfully', | |
| }); | |
| onClose(); | |
| } catch (error) { | |
| setNotification({ | |
| type: 'error', | |
| message: 'Failed to save settings', | |
| }); | |
| } finally { | |
| setIsSaving(false); | |
| } | |
| }; | |
| return ( | |
| <Modal isOpen={isOpen} onClose={onClose} title="Settings" size="md"> | |
| <motion.div className="space-y-6"> | |
| {/* Tailoring Mode Section */} | |
| <Card> | |
| <div className="flex items-center justify-between mb-4"> | |
| <div> | |
| <h3 className="font-semibold text-white flex items-center gap-2"> | |
| <Zap className="w-5 h-5 text-emerald-400" /> | |
| CV Tailoring Mode | |
| </h3> | |
| <p className="text-xs text-slate-400 mt-1"> | |
| Choose how your CVs are tailored to job descriptions | |
| </p> | |
| </div> | |
| </div> | |
| <div className="space-y-3"> | |
| {[ | |
| { | |
| mode: 'local' as TailoringMode, | |
| label: 'Smart Mode (Local)', | |
| description: 'Uses NLP-based skill matching', | |
| icon: ToggleLeft, | |
| }, | |
| { | |
| mode: 'api' as TailoringMode, | |
| label: 'AI Mode (API)', | |
| description: 'Uses advanced AI models for better tailoring', | |
| icon: ToggleRight, | |
| }, | |
| ].map((option) => ( | |
| <motion.button | |
| key={option.mode} | |
| whileHover={{ x: 4 }} | |
| onClick={() => setDraftMode(option.mode)} | |
| className={`w-full p-3 rounded-lg border transition-all ${ | |
| draftMode === option.mode | |
| ? 'border-emerald-500 bg-emerald-500/10' | |
| : 'border-slate-700 bg-slate-800/30 hover:border-emerald-400/30' | |
| }`} | |
| disabled={isLoading || isSaving} | |
| > | |
| <div className="flex items-center gap-3"> | |
| <option.icon | |
| className={`w-5 h-5 ${ | |
| draftMode === option.mode | |
| ? 'text-emerald-400' | |
| : 'text-slate-400' | |
| }`} | |
| /> | |
| <div className="text-left"> | |
| <p className="font-medium text-white text-sm"> | |
| {option.label} | |
| </p> | |
| <p className="text-xs text-slate-400"> | |
| {option.description} | |
| </p> | |
| </div> | |
| {draftMode === option.mode && ( | |
| <div className="ml-auto w-2 h-2 bg-emerald-400 rounded-full" /> | |
| )} | |
| </div> | |
| </motion.button> | |
| ))} | |
| </div> | |
| </Card> | |
| {/* LLM Provider Section (if API mode) */} | |
| {draftMode === 'api' && ( | |
| <Card> | |
| <h3 className="font-semibold text-white mb-4"> | |
| AI Model Provider | |
| </h3> | |
| <Select | |
| label="Select Provider" | |
| value={draftProvider} | |
| onChange={(e) => setDraftProvider(e.target.value as LLMProvider)} | |
| options={llmProviders.map((p) => ({ | |
| value: p.value, | |
| label: `${p.label} - ${p.description}`, | |
| }))} | |
| /> | |
| <p className="text-xs text-slate-400 mt-3"> | |
| Each provider has different speeds and quality levels. Get API keys | |
| from the provider's website. | |
| </p> | |
| </Card> | |
| )} | |
| {/* Advanced Settings */} | |
| <Card> | |
| <h3 className="font-semibold text-white mb-4">Advanced Options</h3> | |
| <div className="space-y-3"> | |
| {[ | |
| { | |
| key: 'summary', | |
| label: 'Enable Professional Summary', | |
| description: 'Automatically generate personalized summaries', | |
| enabled: enableProfessionalSummary, | |
| onToggle: () => setEnableProfessionalSummary((prev) => !prev), | |
| }, | |
| { | |
| key: 'cover', | |
| label: 'Include Cover Letters', | |
| description: 'Generate cover letters along with CVs', | |
| enabled: includeCoverLetters, | |
| onToggle: () => setIncludeCoverLetters((prev) => !prev), | |
| }, | |
| ].map((option) => ( | |
| <motion.div | |
| key={option.key} | |
| whileHover={{ x: 2 }} | |
| className={`flex items-center justify-between p-3 bg-slate-800/30 border rounded-lg transition-colors ${ | |
| option.enabled | |
| ? 'border-emerald-500/40' | |
| : 'border-slate-700 hover:border-slate-600' | |
| }`} | |
| > | |
| <div> | |
| <p className="font-medium text-white text-sm"> | |
| {option.label} | |
| </p> | |
| <p className="text-xs text-slate-400"> | |
| {option.description} | |
| </p> | |
| </div> | |
| <motion.button | |
| whileTap={{ scale: 0.95 }} | |
| onClick={option.onToggle} | |
| disabled={isLoading || isSaving} | |
| className={`w-7 h-7 rounded-md border-2 flex items-center justify-center transition-colors ${ | |
| option.enabled | |
| ? 'bg-emerald-500/20 border-emerald-400 text-emerald-300' | |
| : 'border-slate-600 text-transparent hover:border-slate-500' | |
| }`} | |
| aria-label={`Toggle ${option.label}`} | |
| > | |
| <Check className="w-4 h-4" /> | |
| </motion.button> | |
| </motion.div> | |
| ))} | |
| </div> | |
| </Card> | |
| {/* Action Buttons */} | |
| <div className="flex gap-3 pt-4 border-t border-slate-700"> | |
| <Button variant="secondary" onClick={onClose} fullWidth> | |
| Close | |
| </Button> | |
| <Button onClick={handleSave} fullWidth loading={isSaving}> | |
| Save Changes | |
| </Button> | |
| </div> | |
| </motion.div> | |
| </Modal> | |
| ); | |
| }; | |
| export default SettingsModal; | |