Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion app/admin/preset/create/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ export default async function PresetCreatePage({
);
}

// Сортируем артистов на сервере, чтобы избежать hydration mismatch
const sortedArtists = [...artistsRaw].sort((a, b) => {
// Используем простую сортировку с явной локалью для детерминированности
return a.title.localeCompare(b.title, "en", { sensitivity: "base" });
});

return (
<div className="min-h-screen bg-gray-100">
<header className="bg-white shadow-sm border-b">
Expand Down Expand Up @@ -90,7 +96,7 @@ export default async function PresetCreatePage({
</header>

<main className="max-w-4xl mx-auto px-4 py-8">
<PresetCreateForm generation={generation} artists={artistsRaw} />
<PresetCreateForm generation={generation} artists={sortedArtists} />
</main>
</div>
);
Expand Down
46 changes: 38 additions & 8 deletions app/api/generate-chain/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ export function generateStaticParams() {

interface GenerateChainRequest {
prompt: string;
songsterrData?: {
url: string;
artist: string;
title: string;
trackType: string;
trackName?: string;
suggestedPart: string;
};
}

interface GenerateChainResponse {
Expand All @@ -19,26 +27,48 @@ interface ErrorResponse {
}

/**
* API endpoint для генерации Chain на основе текстового описания
* API endpoint для генерации Chain на основе текстового описания или промпта из Songsterr
* POST /api/generate-chain
* Body: { prompt: string }
* Body: { prompt: string, songsterrData?: {...} }
*/
export async function POST(
request: Request
request: Request,
): Promise<NextResponse<GenerateChainResponse | ErrorResponse>> {
try {
// Получаем prompt из тела запроса
const { prompt } = (await request.json()) as GenerateChainRequest;
// Получаем prompt и опциональные метаданные из тела запроса
const { prompt, songsterrData } =
(await request.json()) as GenerateChainRequest;

if (!prompt || typeof prompt !== "string") {
return NextResponse.json(
{ error: "Prompt is required" },
{ status: 400 }
{ status: 400 },
);
}

console.log(`🎸 Generating chain from prompt: "${prompt}"`);
if (songsterrData) {
console.log(
`📊 With Songsterr metadata: ${songsterrData.artist} - ${songsterrData.title} (${songsterrData.suggestedPart})`,
);
}

const generator = await createGenerator();
const generationId = await generator.generate(prompt);
const generationId = await generator.generate(
prompt,
songsterrData?.url,
songsterrData
? {
artist: songsterrData.artist,
title: songsterrData.title,
trackType: songsterrData.trackType,
suggestedPart: songsterrData.suggestedPart,
...(songsterrData.trackName
? { trackName: songsterrData.trackName }
: {}),
}
: undefined,
);

// Формируем ответ
const response: GenerateChainResponse = {
Expand All @@ -54,7 +84,7 @@ export async function POST(
{
error: error instanceof Error ? error.message : "Internal server error",
},
{ status: 500 }
{ status: 500 },
);
}
}
6 changes: 3 additions & 3 deletions app/api/preset/create/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ interface CreatePresetRequest {
imageUrl?: string | null;
tabsUrl?: string;
pickup: {
type: string;
tone: number;
position: string;
type: "humbucker" | "single";
tone: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
position: "neck" | "bridge" | "middle";
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
import { NextResponse } from "next/server";
import { createGenerator } from "../../../lib/ai-generator/create-generator";
import {
extractSongsterrId,
fetchSongsterrData,
buildPromptWithMetadata,
} from "../../../lib/utils/songsterr";
import { generateFullPrompt } from "../../../lib/utils/track-name-generator";

// Исключаем этот API роут из статической генерации
export function generateStaticParams() {
return [{ id: "this-is-a-dummy-id-for-static-build" }];
}

interface GenerateFromSongsterrRequest {
interface SongsterrToPromptRequest {
songsterrUrl: string;
trackType?: string; // "Rhythm" | "Solo" | "Lead"
}

interface GenerateFromSongsterrResponse {
generationId: string;
message: string;
interface SongsterrToPromptResponse {
prompt: string;
songData: {
metadata: {
url: string;
artist: string;
title: string;
trackType: string;
trackName?: string;
suggestedPart: string;
};
}

Expand All @@ -31,17 +33,17 @@ interface ErrorResponse {
}

/**
* API endpoint для генерации Chain на основе ссылки Songsterr
* POST /api/generate-from-songsterr
* API endpoint для генерации промпта из ссылки Songsterr
* POST /api/songsterr-to-prompt
* Body: { songsterrUrl: string, trackType?: string }
*/
export async function POST(
request: Request,
): Promise<NextResponse<GenerateFromSongsterrResponse | ErrorResponse>> {
): Promise<NextResponse<SongsterrToPromptResponse | ErrorResponse>> {
try {
// Получаем данные из тела запроса
const { songsterrUrl, trackType } =
(await request.json()) as GenerateFromSongsterrRequest;
(await request.json()) as SongsterrToPromptRequest;

// Валидация входных данных
if (!songsterrUrl || typeof songsterrUrl !== "string") {
Expand All @@ -51,7 +53,9 @@ export async function POST(
);
}

// Шаг 1: Извлекаем ID из URL
console.log(`🎸 Generating prompt from Songsterr URL: ${songsterrUrl}`);

// Шаг 1: Извлекаем songId и trackId из URL
const extracted = extractSongsterrId(songsterrUrl);
if (!extracted) {
return NextResponse.json(
Expand Down Expand Up @@ -84,36 +88,50 @@ export async function POST(
);
}

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

// Шаг 4: Запускаем генератор с сформированным промптом
const generator = await createGenerator();
const generationId: string = await generator.generate(
promptResult.prompt,
songsterrUrl,
promptResult.metadata,
);
// Шаг 4: Генерируем полный промпт через AI с полной свободой
const trackNameData: {
artist: string;
title: string;
trackType: string;
trackName?: string;
} = {
artist: promptResult.metadata.artist,
title: promptResult.metadata.title,
trackType: promptResult.metadata.trackType,
...(promptResult.metadata.trackName
? { trackName: promptResult.metadata.trackName }
: {}),
};
const finalPrompt = await generateFullPrompt(trackNameData);
console.log(`💡 AI generated full prompt: "${finalPrompt}"`);

console.log(`✅ Generation created with ID: ${generationId}`);
// Извлекаем suggestedPart из промпта (простая эвристика для обратной совместимости)
const words = finalPrompt.split(" ");
const suggestedPart = words.slice(-3).join(" "); // последние 3 слова

// Формируем ответ
const response: GenerateFromSongsterrResponse = {
generationId,
message: "Generation created successfully from Songsterr URL",
prompt: promptResult.prompt,
songData: {
artist: songData.artist,
title: songData.title,
const response: SongsterrToPromptResponse = {
prompt: finalPrompt,
metadata: {
url: songsterrUrl,
artist: promptResult.metadata.artist,
title: promptResult.metadata.title,
trackType: promptResult.metadata.trackType,
suggestedPart,
...(promptResult.metadata.trackName
? { trackName: promptResult.metadata.trackName }
: {}),
},
};

// Отправляем ответ
console.log(`✅ Prompt generated successfully`);
return NextResponse.json(response);
} catch (error) {
console.error("Error in generate-from-songsterr API:", error);
console.error("Error generating prompt from Songsterr:", error);
return NextResponse.json(
{
error: error instanceof Error ? error.message : "Internal server error",
Expand Down
55 changes: 46 additions & 9 deletions components/GeneratorForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function GeneratorForm(): React.ReactElement {
setIsLoading(false);
}
} else {
// Генерация из Songsterr URL
// Генерация из Songsterr URL (два последовательных вызова)
if (!songsterrUrl.trim()) {
setError("Пожалуйста, введите ссылку на Songsterr");
return;
Expand All @@ -64,7 +64,9 @@ export function GeneratorForm(): React.ReactElement {
setError("");

try {
const response = await fetch("/api/generate-from-songsterr", {
// Шаг 1: Генерация промпта из Songsterr URL
console.log("Step 1: Generating prompt from Songsterr...");
const promptResponse = await fetch("/api/songsterr-to-prompt", {
method: "POST",
headers: {
"Content-Type": "application/json",
Expand All @@ -75,21 +77,56 @@ export function GeneratorForm(): React.ReactElement {
}),
});

if (!response.ok) {
const errorData = (await response.json()) as { error: string };
throw new Error(errorData.error || `Ошибка: ${response.statusText}`);
if (!promptResponse.ok) {
const errorData = (await promptResponse.json()) as { error: string };
throw new Error(
errorData.error || `Ошибка: ${promptResponse.statusText}`,
);
}

const data = (await response.json()) as {
const promptData = (await promptResponse.json()) as {
prompt: string;
metadata: {
url: string;
artist: string;
title: string;
trackType: string;
trackName?: string;
suggestedPart: string;
};
};

console.log(`Step 1 complete: "${promptData.prompt}"`);

// Шаг 2: Генерация цепи из промпта
console.log("Step 2: Generating chain...");
const chainResponse = await fetch("/api/generate-chain", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: promptData.prompt,
songsterrData: promptData.metadata,
}),
});

if (!chainResponse.ok) {
const errorData = (await chainResponse.json()) as { error: string };
throw new Error(
errorData.error || `Ошибка: ${chainResponse.statusText}`,
);
}

const chainData = (await chainResponse.json()) as {
generationId: string;
message: string;
prompt: string;
};

console.log(`Generated prompt from Songsterr: ${data.prompt}`);
console.log("Step 2 complete: Chain generated");

// Перенаправляем на страницу с результатом генерации
router.push(`/admin/generation/${data.generationId}`);
router.push(`/admin/generation/${chainData.generationId}`);
} catch (err) {
setError(err instanceof Error ? err.message : "Произошла ошибка");
setIsLoading(false);
Expand Down
Loading
Loading