feat: Text generation suggestions via Gemini models
Backend:
- New text_gen.py module with Gemini text generation client
- suggest_prompts(): generates 3 prompt suggestions from user input
- get_text_gen_config(): loads config from .env (model, API key, safety)
- get_available_models(): returns list of Gemini text models
- Safety settings: harassment, hate_speech, sexually_explicit, dangerous
Configurable via TEXT_GEN_SAFETY_* env vars (default: BLOCK_ONLY_HIGH)
- New endpoints:
* POST /api/text-gen/suggest — generates 3 prompt suggestions
* GET /api/text-gen/models — lists available text models
* GET /api/text-gen/config — returns safe config (API key masked)
* POST /api/text-gen/config — updates config in-memory
- Default model: gemini-2.5-flash
- Fallback API key: LYRIA_API_KEY if TEXT_GEN_API_KEY not set
- System prompt: expert music producer persona for rich musical detail
Frontend:
- New SuggestionsModal component for displaying generated suggestions
- Auto-fetches when opened
- Shows loading spinner, error state, or suggestion list
- Each suggestion clickable to populate the prompt field
- Hover effect: 'Click to use this prompt →'
- Generator component updated with 'Get Suggestions ✨' button
- Appears next to prompt label in both Basic and Advanced modes
- Disabled when no input present
- Clicking suggestion populates the prompt/structured fields
- SettingsModal expanded with Text Generation section (collapsible)
- Same pattern as Image Generation: model, API key status, safety filters
- Editable dropdowns with Save Changes button
- Refactored to use shared useGenSettings hook for both sections
Models Available:
- gemini-2.5-flash (GA, default) — best price-to-performance
- gemini-2.5-pro (GA) — highest quality reasoning
- gemini-3.5-flash (Preview) — next-gen Flash
- gemini-3.1-flash (Preview) — latest with thinking
Architecture KB and Tasks KB updated.
This commit is contained in:
1 parent
70beb89ad9
commit
e687f7d457
5 files changed
+547
-168
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import StructuredPromptEditor, { PromptStructure } from './StructuredPromptEditor'
|
||||
import SuggestionsModal from './SuggestionsModal'
|
||||
|
||||
interface ModelInfo {
|
||||
name: string
|
||||
@@ -23,6 +24,7 @@ export default function Generator({ onGenerate }: Props) {
|
||||
const [models, setModels] = useState<Record<string, ModelInfo>>({})
|
||||
const [promptMode, setPromptMode] = useState<PromptMode>('basic')
|
||||
const [structuredPrompt, setStructuredPrompt] = useState<PromptStructure>({})
|
||||
const [suggestionsOpen, setSuggestionsOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/models')
|
||||
@@ -181,9 +183,17 @@ export default function Generator({ onGenerate }: Props) {
|
||||
{/* Basic Prompt */}
|
||||
{(promptMode === 'basic' || !isPro) && (
|
||||
<div>
|
||||
<label className="block text-xs text-lyria-muted mb-1.5">
|
||||
Prompt
|
||||
</label>
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<label className="text-xs text-lyria-muted">Prompt</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSuggestionsOpen(true)}
|
||||
disabled={!prompt.trim()}
|
||||
className="text-xs text-lyria-accent hover:text-lyria-accent/80 transition-colors disabled:opacity-40"
|
||||
>
|
||||
Get Suggestions ✨
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
value={prompt}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
@@ -196,9 +206,17 @@ export default function Generator({ onGenerate }: Props) {
|
||||
{/* Advanced Prompt */}
|
||||
{promptMode === 'advanced' && isPro && (
|
||||
<div>
|
||||
<label className="block text-xs text-lyria-muted mb-1.5">
|
||||
Structured Prompt
|
||||
</label>
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<label className="text-xs text-lyria-muted">Structured Prompt</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSuggestionsOpen(true)}
|
||||
disabled={Object.values(structuredPrompt).every(v => !v?.trim())}
|
||||
className="text-xs text-lyria-accent hover:text-lyria-accent/80 transition-colors disabled:opacity-40"
|
||||
>
|
||||
Get Suggestions ✨
|
||||
</button>
|
||||
</div>
|
||||
<StructuredPromptEditor
|
||||
value={structuredPrompt}
|
||||
onChange={setStructuredPrompt}
|
||||
@@ -218,6 +236,20 @@ export default function Generator({ onGenerate }: Props) {
|
||||
{isLoading ? 'Generating...' : 'Generate'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<SuggestionsModal
|
||||
isOpen={suggestionsOpen}
|
||||
onClose={() => setSuggestionsOpen(false)}
|
||||
onSelect={(suggestion) => {
|
||||
if (promptMode === 'basic' || !isPro) {
|
||||
setPrompt(suggestion)
|
||||
} else {
|
||||
// For advanced mode, put it in the 'style' field or additional
|
||||
setStructuredPrompt(prev => ({ ...prev, additional: suggestion }))
|
||||
}
|
||||
}}
|
||||
userInput={promptMode === 'basic' || !isPro ? prompt : Object.values(structuredPrompt).filter(Boolean).join(' ')}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user