feat: Add Global Module Settings and Caption to Images

- Introduced GlobalModuleSettings model for platform-wide module enable/disable settings.
- Added 'caption' field to Images model to store image captions.
- Updated GenerateImagePromptsFunction to handle new caption structure in prompts.
- Enhanced AIPromptViewSet to return global prompt types and validate active prompts.
- Modified serializers and views to accommodate new caption field and global settings.
- Updated frontend components to display captions and filter prompts based on active types.
- Created migrations for GlobalModuleSettings and added caption field to Images.
This commit is contained in:
IGNY8 VPS (Salman)
2025-12-20 21:34:59 +00:00
parent 9e8ff4fbb1
commit 7a1e952a57
16 changed files with 370 additions and 383 deletions

View File

@@ -73,6 +73,7 @@ export default function Prompts() {
const [prompts, setPrompts] = useState<Record<string, PromptData>>({});
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState<Record<string, boolean>>({});
const [activePromptTypes, setActivePromptTypes] = useState<string[]>([]);
// Load all prompts
useEffect(() => {
@@ -82,7 +83,15 @@ export default function Prompts() {
const loadPrompts = async () => {
setLoading(true);
try {
const promises = PROMPT_TYPES.map(async (type) => {
// First, get the list of globally active prompt types
const activeTypesResponse = await fetchAPI('/v1/system/prompts/active_types/');
const activeTypes = activeTypesResponse.active_types || [];
setActivePromptTypes(activeTypes);
// Only load prompts that are globally active
const activePromptConfigs = PROMPT_TYPES.filter(type => activeTypes.includes(type.key));
const promises = activePromptConfigs.map(async (type) => {
try {
// fetchAPI extracts data from unified format {success: true, data: {...}}
// So response IS the data object
@@ -100,15 +109,6 @@ export default function Prompts() {
results.forEach(({ key, data }) => {
if (data) {
promptsMap[key] = data;
} else {
// Use default if not found
promptsMap[key] = {
prompt_type: key,
prompt_type_display: PROMPT_TYPES.find(t => t.key === key)?.label || key,
prompt_value: '',
default_prompt: '',
is_active: true,
};
}
});
@@ -218,6 +218,7 @@ export default function Prompts() {
<div className="p-6">
{/* Planner Prompts Section */}
{PROMPT_TYPES.filter(t => ['clustering', 'ideas'].includes(t.key) && activePromptTypes.includes(t.key)).length > 0 && (
<div className="mb-8">
<div className="mb-4">
<h2 className="text-xl font-semibold text-gray-800 dark:text-white mb-1">
@@ -231,7 +232,7 @@ export default function Prompts() {
{/* 2-Column Grid for Planner Prompts */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Clustering Prompt */}
{PROMPT_TYPES.filter(t => ['clustering', 'ideas'].includes(t.key)).map((type) => {
{PROMPT_TYPES.filter(t => ['clustering', 'ideas'].includes(t.key) && activePromptTypes.includes(t.key)).map((type) => {
const prompt = prompts[type.key] || {
prompt_type: type.key,
prompt_type_display: type.label,
@@ -287,8 +288,10 @@ export default function Prompts() {
})}
</div>
</div>
)}
{/* Writer Prompts Section */}
{PROMPT_TYPES.filter(t => t.key === 'content_generation' && activePromptTypes.includes(t.key)).length > 0 && (
<div className="mb-8">
<div className="mb-4">
<h2 className="text-xl font-semibold text-gray-800 dark:text-white mb-1">
@@ -301,7 +304,7 @@ export default function Prompts() {
{/* Content Generation Prompt */}
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-gray-900">
{PROMPT_TYPES.filter(t => t.key === 'content_generation').map((type) => {
{PROMPT_TYPES.filter(t => t.key === 'content_generation' && activePromptTypes.includes(t.key)).map((type) => {
const prompt = prompts[type.key] || {
prompt_type: type.key,
prompt_type_display: type.label,
@@ -357,8 +360,10 @@ export default function Prompts() {
})}
</div>
</div>
)}
{/* Image Generation Section */}
{PROMPT_TYPES.filter(t => ['image_prompt_extraction', 'image_prompt_template', 'negative_prompt'].includes(t.key) && activePromptTypes.includes(t.key)).length > 0 && (
<div className="mb-8">
<div className="mb-4">
<h2 className="text-xl font-semibold text-gray-800 dark:text-white mb-1">
@@ -371,7 +376,7 @@ export default function Prompts() {
{/* 2-Column Grid for Image Prompts */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{PROMPT_TYPES.filter(t => ['image_prompt_extraction', 'image_prompt_template', 'negative_prompt'].includes(t.key)).map((type) => {
{PROMPT_TYPES.filter(t => ['image_prompt_extraction', 'image_prompt_template', 'negative_prompt'].includes(t.key) && activePromptTypes.includes(t.key)).map((type) => {
const prompt = prompts[type.key] || {
prompt_type: type.key,
prompt_type_display: type.label,
@@ -429,8 +434,10 @@ export default function Prompts() {
})}
</div>
</div>
)}
{/* Site Builder Prompts Section */}
{PROMPT_TYPES.filter(t => t.key === 'site_structure_generation' && activePromptTypes.includes(t.key)).length > 0 && (
<div className="mb-8">
<div className="mb-4">
<h2 className="text-xl font-semibold text-gray-800 dark:text-white mb-1">
@@ -443,7 +450,7 @@ export default function Prompts() {
{/* Site Structure Generation Prompt */}
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-gray-900">
{PROMPT_TYPES.filter(t => t.key === 'site_structure_generation').map((type) => {
{PROMPT_TYPES.filter(t => t.key === 'site_structure_generation' && activePromptTypes.includes(t.key)).map((type) => {
const prompt = prompts[type.key] || {
prompt_type: type.key,
prompt_type_display: type.label,
@@ -508,6 +515,7 @@ export default function Prompts() {
})}
</div>
</div>
)}
</div>
</>
);

View File

@@ -1377,6 +1377,7 @@ export interface ImageRecord {
image_url?: string | null;
image_path?: string | null;
prompt?: string | null;
caption?: string | null;
status: string;
position: number;
created_at: string;

View File

@@ -21,6 +21,9 @@ import {
AccountSettingsError,
} from '../services/api';
// Version for cache busting - increment when structure changes
const SETTINGS_STORE_VERSION = 2;
const getAccountSettingsErrorMessage = (error: AccountSettingsError): string => {
switch (error.type) {
case 'ACCOUNT_SETTINGS_NOT_FOUND':
@@ -241,11 +244,23 @@ export const useSettingsStore = create<SettingsState>()(
}),
{
name: 'settings-storage',
version: SETTINGS_STORE_VERSION, // Add version for cache busting
partialize: (state) => ({
accountSettings: state.accountSettings,
moduleSettings: state.moduleSettings,
moduleEnableSettings: state.moduleEnableSettings,
}),
// Migrate function to handle version changes
migrate: (persistedState: any, version: number) => {
if (version < SETTINGS_STORE_VERSION) {
// Clear module enable settings on version upgrade
return {
...persistedState,
moduleEnableSettings: null,
};
}
return persistedState;
},
}
)
);

View File

@@ -275,15 +275,15 @@ const FeaturedImageBlock = ({
) : (
<PromptPlaceholder prompt={image?.prompt} minHeight={420} label="Featured Image Prompt" />
)}
{image?.prompt && imageSrc && (
{image?.caption && imageSrc && (
<div className="absolute bottom-5 left-5 rounded-full bg-white/80 px-4 py-2 text-xs font-medium text-slate-600 backdrop-blur-sm dark:bg-gray-950/70 dark:text-slate-300">
Prompt aligned to hero section
Caption aligned to hero section
</div>
)}
</div>
{image?.prompt && (
{image?.caption && (
<div className="border-t border-slate-200/70 bg-white/70 px-8 py-6 text-sm leading-relaxed text-slate-600 backdrop-blur-sm dark:border-gray-800/60 dark:bg-gray-900/70 dark:text-slate-300">
{image.prompt}
{image.caption}
</div>
)}
</div>
@@ -322,12 +322,12 @@ const SectionImageBlock = ({
<ImageStatusPill status={image?.status} />
</div>
</div>
{image?.prompt && (
{image?.caption && (
<figcaption className="space-y-3 px-6 py-5 text-sm leading-relaxed text-slate-600 dark:text-slate-300">
<p className="font-semibold uppercase tracking-[0.25em] text-slate-400 dark:text-slate-500">
Visual Direction
Image Caption
</p>
<p className="font-medium whitespace-pre-wrap">{image.prompt}</p>
<p className="font-medium whitespace-pre-wrap">{image.caption}</p>
</figcaption>
)}
</figure>