Skip to content

Commit 2a67eac

Browse files
committed
Add API endpoint for generating music prompts from Songsterr URLs and update GeneratorForm for Songsterr integration
1 parent 054db3c commit 2a67eac

File tree

6 files changed

+300
-10
lines changed

6 files changed

+300
-10
lines changed

app/api/generate-from-songsterr/route.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { createGenerator } from "../../../lib/ai-generator/create-generator";
33
import {
44
extractSongsterrId,
55
fetchSongsterrData,
6-
buildPromptFromSongsterr,
6+
buildPromptWithMetadata,
77
} from "../../../lib/utils/songsterr";
88

99
// Исключаем этот API роут из статической генерации
@@ -84,21 +84,26 @@ export async function POST(
8484
);
8585
}
8686

87-
// Шаг 3: Формируем промпт с учетом конкретного trackId из URL
88-
const prompt = buildPromptFromSongsterr(songData, trackType, trackId);
89-
console.log(`💡 Generated prompt: "${prompt}"`);
87+
// Шаг 3: Формируем промпт с метаданными с учетом конкретного trackId из URL
88+
const promptResult = buildPromptWithMetadata(songData, trackType, trackId);
89+
console.log(`💡 Generated prompt: "${promptResult.prompt}"`);
90+
console.log("📊 Metadata:", promptResult.metadata);
9091

9192
// Шаг 4: Запускаем генератор с сформированным промптом
9293
const generator = await createGenerator();
93-
const generationId = await generator.generate(prompt);
94+
const generationId: string = await generator.generate(
95+
promptResult.prompt,
96+
songsterrUrl,
97+
promptResult.metadata,
98+
);
9499

95100
console.log(`✅ Generation created with ID: ${generationId}`);
96101

97102
// Формируем ответ
98103
const response: GenerateFromSongsterrResponse = {
99104
generationId,
100105
message: "Generation created successfully from Songsterr URL",
101-
prompt,
106+
prompt: promptResult.prompt,
102107
songData: {
103108
artist: songData.artist,
104109
title: songData.title,

components/PresetCreateForm.tsx

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
"use client";
22

3-
import { useState, useMemo } from "react";
3+
import { useState, useMemo, useEffect } from "react";
44
import { useRouter } from "next/navigation";
55
import Image from "next/image";
66
import { GenerationRecord } from "../lib/jsondb/types";
77
import { ValidatedArtist } from "../lib/public/schemas/artist";
8+
import { smartMapToPart } from "../lib/utils/track-mapping";
89

910
interface PresetCreateFormProps {
1011
generation: GenerationRecord;
@@ -50,6 +51,53 @@ export function PresetCreateForm({
5051
},
5152
});
5253

54+
// Автозаполнение формы из songsterrData
55+
useEffect(() => {
56+
if (!generation.songsterrData) return;
57+
58+
const { artist, title, trackType, trackName, url } =
59+
generation.songsterrData;
60+
61+
// 1. Поиск существующего артиста (fuzzy match)
62+
const foundArtist = artists.find(
63+
(a) =>
64+
a.title.toLowerCase() === artist.toLowerCase() ||
65+
a.title.toLowerCase().includes(artist.toLowerCase()) ||
66+
artist.toLowerCase().includes(a.title.toLowerCase()),
67+
);
68+
69+
if (foundArtist) {
70+
// Артист найден - выбираем его
71+
setIsNewArtist(false);
72+
setFormData((prev) => ({
73+
...prev,
74+
artistId: foundArtist.id,
75+
song: title,
76+
part: smartMapToPart(trackType, trackName),
77+
tabsUrl: url,
78+
}));
79+
console.log(
80+
`✅ Артист найден: ${foundArtist.title} (ID: ${String(foundArtist.id)})`,
81+
);
82+
} else {
83+
// Артист не найден - создаем нового
84+
setIsNewArtist(true);
85+
setFormData((prev) => ({
86+
...prev,
87+
newArtistTitle: artist,
88+
newArtistDescription: `Описание для ${artist}`,
89+
song: title,
90+
part: smartMapToPart(trackType, trackName),
91+
tabsUrl: url,
92+
}));
93+
console.log(`📝 Создание нового артиста: ${artist}`);
94+
}
95+
96+
console.log(
97+
`🎵 Автозаполнение: ${artist} - ${title} (${smartMapToPart(trackType, trackName)})`,
98+
);
99+
}, [generation.songsterrData, artists]);
100+
53101
// Получаем последнюю версию chain из генерации (не используется в UI, но может понадобиться)
54102
const latestChain = useMemo(() => {
55103
if (generation.versions.length === 0) return null;
@@ -163,6 +211,33 @@ export function PresetCreateForm({
163211
}}
164212
className="space-y-6"
165213
>
214+
{/* Автозаполнение из Songsterr */}
215+
{generation.songsterrData && (
216+
<div className="bg-green-50 border border-green-200 rounded-lg p-4 mb-6">
217+
<h3 className="text-lg font-semibold text-green-900 mb-2">
218+
✅ Форма автозаполнена из Songsterr
219+
</h3>
220+
<div className="text-sm text-green-700 space-y-1">
221+
<p>
222+
<span className="font-medium">Артист:</span>{" "}
223+
{generation.songsterrData.artist}
224+
</p>
225+
<p>
226+
<span className="font-medium">Песня:</span>{" "}
227+
{generation.songsterrData.title}
228+
</p>
229+
<p>
230+
<span className="font-medium">Тип трека:</span>{" "}
231+
{generation.songsterrData.trackType}
232+
</p>
233+
<p className="text-xs text-green-600 mt-2">
234+
💡 Проверьте автозаполненные поля ниже и внесите правки при
235+
необходимости
236+
</p>
237+
</div>
238+
</div>
239+
)}
240+
166241
{/* Информация о генерации */}
167242
<div className="bg-gray-50 rounded-lg p-4">
168243
<h3 className="text-lg font-semibold text-gray-900 mb-3">

lib/ai-generator/create-generator.ts

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,16 @@ interface RealRig {
3535
class ChainGenerator {
3636
constructor(private openai: OpenAI) {}
3737

38-
async generate(prompt: string): Promise<string> {
38+
async generate(
39+
prompt: string,
40+
songsterrUrl?: string,
41+
songsterrMetadata?: {
42+
artist: string;
43+
title: string;
44+
trackType: string;
45+
trackName?: string;
46+
},
47+
): Promise<string> {
3948
const proDescription = await this.createProDescription(prompt);
4049
const realRig = await this.createRealRig(proDescription);
4150
const emptyChain = await this.createEmptyChain(realRig);
@@ -46,7 +55,22 @@ class ChainGenerator {
4655

4756
// Сохраняем результаты в базу данных
4857
const now = new Date().toISOString();
49-
const generationId = await this.saveToDatabase({
58+
59+
const dataToSave: {
60+
timestamp: string;
61+
originalPrompt: string;
62+
proDescription: ProDescription;
63+
realRig: RealRig;
64+
finalChain: Chain;
65+
versions: Array<{ chain: Chain; prompt: string; timestamp: string }>;
66+
songsterrData?: {
67+
url: string;
68+
artist: string;
69+
title: string;
70+
trackType: string;
71+
trackName?: string;
72+
};
73+
} = {
5074
timestamp: now,
5175
originalPrompt: prompt,
5276
proDescription,
@@ -59,7 +83,21 @@ class ChainGenerator {
5983
timestamp: now,
6084
},
6185
],
62-
});
86+
};
87+
88+
if (songsterrUrl && songsterrMetadata) {
89+
dataToSave.songsterrData = {
90+
url: songsterrUrl,
91+
artist: songsterrMetadata.artist,
92+
title: songsterrMetadata.title,
93+
trackType: songsterrMetadata.trackType,
94+
...(songsterrMetadata.trackName
95+
? { trackName: songsterrMetadata.trackName }
96+
: {}),
97+
};
98+
}
99+
100+
const generationId = await this.saveToDatabase(dataToSave);
63101

64102
return generationId;
65103
}
@@ -149,6 +187,13 @@ class ChainGenerator {
149187
realRig: RealRig;
150188
finalChain: Chain;
151189
versions: Array<{ chain: Chain; prompt: string; timestamp: string }>;
190+
songsterrData?: {
191+
url: string;
192+
artist: string;
193+
title: string;
194+
trackType: string;
195+
trackName?: string;
196+
};
152197
}): Promise<string> {
153198
try {
154199
const generationId = await generationDb.addGeneration(result);

lib/jsondb/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ export interface GenerationRecord {
3030
settings: unknown;
3131
};
3232
versions: GenerationVersion[];
33+
songsterrData?: {
34+
url: string;
35+
artist: string;
36+
title: string;
37+
trackType: string;
38+
trackName?: string;
39+
};
3340
}
3441

3542
export interface JsonDatabase {

lib/utils/songsterr.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,16 @@ function detectGuitarType(instrument: string): string | null {
147147
return null;
148148
}
149149

150+
export interface SongsterrPromptResult {
151+
prompt: string;
152+
metadata: {
153+
artist: string;
154+
title: string;
155+
trackType: string;
156+
trackName?: string;
157+
};
158+
}
159+
150160
/**
151161
* Формирует промпт для генератора из данных Songsterr
152162
* @param songData Данные о песне из Songsterr
@@ -236,6 +246,92 @@ export function buildPromptFromSongsterr(
236246
return prompt;
237247
}
238248

249+
/**
250+
* Формирует промпт и метаданные для генератора из данных Songsterr
251+
* @param songData Данные о песне из Songsterr
252+
* @param trackType Опциональный тип трека (Rhythm/Solo/Lead)
253+
* @param specificTrackId Опциональный ID конкретного трека (partId из URL)
254+
* @returns Объект с промптом и метаданными
255+
*/
256+
export function buildPromptWithMetadata(
257+
songData: SongsterrSongData,
258+
trackType?: string,
259+
specificTrackId?: number | null,
260+
): SongsterrPromptResult {
261+
const artist = songData.artist;
262+
const title = songData.title;
263+
264+
// Если trackType не указан, определяем автоматически
265+
let guitarType = trackType;
266+
let selectedTrackName: string | undefined;
267+
268+
if (!guitarType && songData.tracks && songData.tracks.length > 0) {
269+
// Фильтруем гитарные треки
270+
const guitarTracks = songData.tracks.filter((t) => t.isGuitar);
271+
272+
if (guitarTracks.length > 0) {
273+
let selectedTrack: SongsterrTrack | undefined;
274+
275+
// Если указан конкретный trackId из URL, используем его
276+
if (specificTrackId !== undefined && specificTrackId !== null) {
277+
selectedTrack = guitarTracks.find((t) => t.partId === specificTrackId);
278+
}
279+
280+
// Если трек не найден или не указан, берем самый популярный
281+
if (!selectedTrack) {
282+
const popularPartId =
283+
songData.popularTrackGuitar !== undefined
284+
? songData.popularTrackGuitar
285+
: (guitarTracks[0]?.partId ?? 0);
286+
287+
selectedTrack = guitarTracks.find((t) => t.partId === popularPartId);
288+
289+
if (!selectedTrack) {
290+
selectedTrack = guitarTracks.reduce((prev, current) =>
291+
current.views > prev.views ? current : prev,
292+
);
293+
}
294+
}
295+
296+
// Сохраняем название трека
297+
selectedTrackName = selectedTrack.title;
298+
299+
// Определяем тип трека по его названию
300+
const trackName = selectedTrack.title.toLowerCase();
301+
302+
if (trackName.includes("lead")) {
303+
guitarType = "Lead";
304+
} else if (trackName.includes("solo")) {
305+
guitarType = "Solo";
306+
} else if (trackName.includes("rhythm")) {
307+
guitarType = "Rhythm";
308+
} else if (trackName.includes("background")) {
309+
guitarType = "Rhythm";
310+
} else {
311+
guitarType = selectedTrack.partId <= 1 ? "Rhythm" : "Lead";
312+
}
313+
}
314+
}
315+
316+
// По умолчанию используем Rhythm
317+
if (!guitarType) {
318+
guitarType = "Rhythm";
319+
}
320+
321+
// Формируем промпт
322+
const prompt = `${artist} ${title} ${guitarType} Guitar Main Riff`;
323+
324+
return {
325+
prompt,
326+
metadata: {
327+
artist,
328+
title,
329+
trackType: guitarType,
330+
...(selectedTrackName ? { trackName: selectedTrackName } : {}),
331+
},
332+
};
333+
}
334+
239335
/**
240336
* Получает список доступных гитарных треков из данных Songsterr
241337
* @param songData Данные о песне

0 commit comments

Comments
 (0)