feat: Structured prompt editing for Pro model

Backend:
- Added prompt_structure JSON column to clips DB (migration in init_db)
- Updated GenerateRequest model with optional prompt_structure dict
- build_prompt() method combines structured fields into single prompt:
  'Generate a track with the following elements:\n\n**Style:** [style]\n...'
- create_clip() accepts and stores prompt_structure JSON
- get_clips()/get_clip() parse prompt_structure back to dict

Frontend:
- New StructuredPromptEditor component with 8 collapsible fields:
  Style, Timbre, Structure, Mood, Tempo, Instrumentation, Effects, Additional
  - Fields show filled/empty state with colored dots
  - Expand/collapse individual fields
  - 'Expand All' button
  - Counter: 'X of 8 fields filled'
- Generator component updated with Basic/Advanced mode toggle (Pro only)
  - Basic: single textarea (works for both models)
  - Advanced: StructuredPromptEditor (Pro only)
  - Mode resets to Basic when switching to Clip Preview
- DetailPanel shows structured prompt breakdown when present
  - Each filled field shown in its own labeled box
  - Style, Timbre, Structure, Mood, Tempo, Instrumentation, Effects, Additional
- Clip interface updated with prompt_structure field across all components

Architecture KB and Tasks KB updated.
This commit is contained in:
rin.kagamine committed 2026-05-29 22:28:52 -04:00
1 parent a79b2806dc
commit 70beb89ad9
8 files changed
+273 -25

No files matched your search

+24
View File
@@ -24,6 +24,7 @@ class GenerateRequest(BaseModel):
prompt: str
model: str = DEFAULT_MODEL
duration: Optional[int] = None # defaults to model default
prompt_structure: Optional[dict] = None # structured prompt fields for Pro mode
@field_validator('model')
@classmethod
@@ -40,6 +41,26 @@ class GenerateRequest(BaseModel):
dur = self.duration if self.duration is not None else info["default_duration"]
return min(dur, info["max_duration"])
def build_prompt(self) -> str:
"""Build the final prompt from structured fields or return the simple prompt."""
if self.prompt_structure:
parts = ["Generate a track with the following elements:"]
field_order = [
("style", "Style"),
("timbre", "Timbre"),
("structure", "Structure"),
("mood", "Mood"),
("tempo", "Tempo"),
("instrumentation", "Instrumentation"),
("effects", "Effects"),
("additional", "Additional Notes"),
]
for key, label in field_order:
if key in self.prompt_structure and self.prompt_structure[key]:
parts.append(f"**{label}:** {self.prompt_structure[key]}")
return "\n\n".join(parts)
return self.prompt
class GenerateResponse(BaseModel):
id: str
status: str
@@ -57,6 +78,9 @@ class Clip(BaseModel):
status: str
error_message: Optional[str] = None
created_at: datetime
prompt_structure: Optional[dict] = None
bpm: Optional[float] = None
key: Optional[str] = None
class RenameRequest(BaseModel):
title: str