generated from fahricansecer/boilerplate-be
@@ -3,6 +3,7 @@ import { PrismaService } from '../../../database/prisma.service';
|
||||
import { GeminiService } from '../../gemini/gemini.service';
|
||||
import { CreateSegmentDto, UpdateSegmentDto } from '../dto';
|
||||
import { AnalysisService } from './analysis.service';
|
||||
import { VersionsService } from './versions.service';
|
||||
import {
|
||||
buildScriptOutlinePrompt,
|
||||
buildChapterSegmentPrompt,
|
||||
@@ -28,6 +29,7 @@ export class ScriptsService {
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly gemini: GeminiService,
|
||||
private readonly analysisService: AnalysisService,
|
||||
private readonly versionsService: VersionsService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -113,6 +115,20 @@ export class ScriptsService {
|
||||
async generateScript(projectId: string) {
|
||||
this.logger.log(`Generating script for project: ${projectId}`);
|
||||
|
||||
// Auto-snapshot current state before regeneration
|
||||
await this.versionsService.createSnapshot(
|
||||
projectId,
|
||||
'AUTO_SAVE',
|
||||
undefined,
|
||||
'Auto-save before script generation',
|
||||
).catch(() => { /* ignore if no segments yet */ });
|
||||
|
||||
// Update status
|
||||
await this.prisma.scriptProject.update({
|
||||
where: { id: projectId },
|
||||
data: { status: 'SCRIPTING' },
|
||||
});
|
||||
|
||||
const project = await this.prisma.scriptProject.findUnique({
|
||||
where: { id: projectId },
|
||||
include: {
|
||||
@@ -364,4 +380,103 @@ export class ScriptsService {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ========== REGENERATE (Tek segment / Partial) ==========
|
||||
|
||||
/**
|
||||
* Regenerate a single segment with AI.
|
||||
* Auto-snapshots current state before regeneration.
|
||||
*
|
||||
* @param segmentId - Segment ID to regenerate
|
||||
* @returns Updated segment
|
||||
*/
|
||||
async regenerateSegment(segmentId: string) {
|
||||
const segment = await this.prisma.scriptSegment.findUnique({
|
||||
where: { id: segmentId },
|
||||
include: { project: { include: { characters: true } } },
|
||||
});
|
||||
|
||||
if (!segment) {
|
||||
throw new NotFoundException(`Segment with ID ${segmentId} not found`);
|
||||
}
|
||||
|
||||
// Auto-snapshot before regeneration
|
||||
await this.versionsService.createSnapshot(
|
||||
segment.projectId,
|
||||
'AUTO_SAVE',
|
||||
undefined,
|
||||
`Auto-save before regenerating segment #${segment.sortOrder + 1}`,
|
||||
).catch(() => {});
|
||||
|
||||
const characterContext = segment.project.characters
|
||||
.map((c) => `${c.name} (${c.role}): Values[${c.values}] Traits[${c.traits}]`)
|
||||
.join('\n');
|
||||
|
||||
const chapterPromptData = buildChapterSegmentPrompt({
|
||||
chapterIndex: segment.sortOrder,
|
||||
totalChapters: 1,
|
||||
chapterTitle: segment.segmentType,
|
||||
chapterFocus: segment.visualDescription || segment.segmentType,
|
||||
chapterType: segment.segmentType,
|
||||
speechStyles: segment.project.speechStyle,
|
||||
targetAudience: segment.project.targetAudience,
|
||||
characterContext,
|
||||
language: segment.project.language,
|
||||
});
|
||||
|
||||
const resp = await this.gemini.generateJSON<any[]>(
|
||||
chapterPromptData.prompt,
|
||||
chapterPromptData.schema,
|
||||
{ temperature: chapterPromptData.temperature },
|
||||
);
|
||||
|
||||
const newSeg = resp.data[0];
|
||||
if (!newSeg) return segment;
|
||||
|
||||
const words = newSeg.narratorScript ? newSeg.narratorScript.split(' ').length : 0;
|
||||
const dur = Math.max(5, Math.ceil(words / (140 / 60)));
|
||||
|
||||
return this.prisma.scriptSegment.update({
|
||||
where: { id: segmentId },
|
||||
data: {
|
||||
narratorScript: newSeg.narratorScript,
|
||||
visualDescription: newSeg.visualDescription,
|
||||
videoPrompt: newSeg.videoPrompt,
|
||||
imagePrompt: newSeg.imagePrompt,
|
||||
onScreenText: newSeg.onScreenText,
|
||||
audioCues: newSeg.audioCues,
|
||||
stockQuery: newSeg.stockQuery,
|
||||
duration: `${dur}s`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate multiple selected segments.
|
||||
*
|
||||
* @param projectId - Project ID
|
||||
* @param segmentIds - Array of segment IDs to regenerate
|
||||
* @returns Updated segments
|
||||
*/
|
||||
async regeneratePartial(projectId: string, segmentIds: string[]) {
|
||||
// Auto-snapshot
|
||||
await this.versionsService.createSnapshot(
|
||||
projectId,
|
||||
'AUTO_SAVE',
|
||||
undefined,
|
||||
`Auto-save before partial regeneration (${segmentIds.length} segments)`,
|
||||
).catch(() => {});
|
||||
|
||||
const results: any[] = [];
|
||||
for (const segId of segmentIds) {
|
||||
try {
|
||||
const result = await this.regenerateSegment(segId);
|
||||
results.push(result);
|
||||
} catch (error) {
|
||||
this.logger.warn(`Failed to regenerate segment ${segId}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user