33import React , { useState } from "react" ;
44import { useRouter } from "next/navigation" ;
55
6+ type InputMode = "text" | "songsterr" ;
7+
68export function GeneratorForm ( ) : React . ReactElement {
79 const router = useRouter ( ) ;
10+ const [ mode , setMode ] = useState < InputMode > ( "text" ) ;
811 const [ prompt , setPrompt ] = useState < string > (
9- "Metallice Enter Sandman Rhythm Guitar Main Riff"
12+ "Metallice Enter Sandman Rhythm Guitar Main Riff" ,
13+ ) ;
14+ const [ songsterrUrl , setSongsterrUrl ] = useState < string > (
15+ "https://www.songsterr.com/a/wsa/amatory-tab-s25195" ,
1016 ) ;
17+ const [ trackType , setTrackType ] = useState < string > ( "auto" ) ;
1118 const [ isLoading , setIsLoading ] = useState < boolean > ( false ) ;
1219 const [ error , setError ] = useState < string > ( "" ) ;
1320
1421 const handleGenerate = async ( ) : Promise < void > => {
15- if ( ! prompt . trim ( ) ) {
16- setError ( "Пожалуйста, введите описание желаемого звука" ) ;
17- return ;
18- }
22+ if ( mode === "text" ) {
23+ // Генерация из текстового промпта
24+ if ( ! prompt . trim ( ) ) {
25+ setError ( "Пожалуйста, введите описание желаемого звука" ) ;
26+ return ;
27+ }
1928
20- setIsLoading ( true ) ;
21- setError ( "" ) ;
29+ setIsLoading ( true ) ;
30+ setError ( "" ) ;
2231
23- try {
24- const response = await fetch ( "/api/generate-chain" , {
25- method : "POST" ,
26- headers : {
27- "Content-Type" : "application/json" ,
28- } ,
29- body : JSON . stringify ( { prompt } ) ,
30- } ) ;
32+ try {
33+ const response = await fetch ( "/api/generate-chain" , {
34+ method : "POST" ,
35+ headers : {
36+ "Content-Type" : "application/json" ,
37+ } ,
38+ body : JSON . stringify ( { prompt } ) ,
39+ } ) ;
3140
32- if ( ! response . ok ) {
33- throw new Error ( `Ошибка: ${ response . statusText } ` ) ;
41+ if ( ! response . ok ) {
42+ throw new Error ( `Ошибка: ${ response . statusText } ` ) ;
43+ }
44+
45+ const data = ( await response . json ( ) ) as {
46+ generationId : string ;
47+ message : string ;
48+ } ;
49+
50+ // Перенаправляем на страницу с результатом генерации
51+ router . push ( `/admin/generation/${ data . generationId } ` ) ;
52+ } catch ( err ) {
53+ setError ( err instanceof Error ? err . message : "Произошла ошибка" ) ;
54+ setIsLoading ( false ) ;
3455 }
56+ } else {
57+ // Генерация из Songsterr URL
58+ if ( ! songsterrUrl . trim ( ) ) {
59+ setError ( "Пожалуйста, введите ссылку на Songsterr" ) ;
60+ return ;
61+ }
62+
63+ setIsLoading ( true ) ;
64+ setError ( "" ) ;
3565
36- const data = ( await response . json ( ) ) as {
37- generationId : string ;
38- message : string ;
39- } ;
66+ try {
67+ const response = await fetch ( "/api/generate-from-songsterr" , {
68+ method : "POST" ,
69+ headers : {
70+ "Content-Type" : "application/json" ,
71+ } ,
72+ body : JSON . stringify ( {
73+ songsterrUrl,
74+ trackType : trackType === "auto" ? undefined : trackType ,
75+ } ) ,
76+ } ) ;
4077
41- // Перенаправляем на страницу с результатом генерации
42- router . push ( `/admin/generation/${ data . generationId } ` ) ;
43- } catch ( err ) {
44- setError ( err instanceof Error ? err . message : "Произошла ошибка" ) ;
45- setIsLoading ( false ) ;
78+ if ( ! response . ok ) {
79+ const errorData = ( await response . json ( ) ) as { error : string } ;
80+ throw new Error ( errorData . error || `Ошибка: ${ response . statusText } ` ) ;
81+ }
82+
83+ const data = ( await response . json ( ) ) as {
84+ generationId : string ;
85+ message : string ;
86+ prompt : string ;
87+ } ;
88+
89+ console . log ( `Generated prompt from Songsterr: ${ data . prompt } ` ) ;
90+
91+ // Перенаправляем на страницу с результатом генерации
92+ router . push ( `/admin/generation/${ data . generationId } ` ) ;
93+ } catch ( err ) {
94+ setError ( err instanceof Error ? err . message : "Произошла ошибка" ) ;
95+ setIsLoading ( false ) ;
96+ }
4697 }
4798 } ;
4899
49100 const handleKeyPress = (
50- e : React . KeyboardEvent < HTMLTextAreaElement >
101+ e : React . KeyboardEvent < HTMLTextAreaElement | HTMLInputElement > ,
51102 ) : void => {
52103 if ( e . key === "Enter" && ! e . shiftKey ) {
53104 e . preventDefault ( ) ;
54105 void handleGenerate ( ) ;
55106 }
56107 } ;
57108
109+ const isDisabled = mode === "text" ? ! prompt . trim ( ) : ! songsterrUrl . trim ( ) ;
110+
58111 return (
59112 < >
60113 { /* Input Section */ }
@@ -64,29 +117,112 @@ export function GeneratorForm(): React.ReactElement {
64117 ✨ Опишите желаемый звук
65118 </ h2 >
66119 </ div >
120+
121+ { /* Tabs */ }
122+ < div className = "border-b border-gray-200" >
123+ < div className = "flex" >
124+ < button
125+ onClick = { ( ) => {
126+ setMode ( "text" ) ;
127+ } }
128+ className = { `px-6 py-3 text-sm font-medium transition-colors ${
129+ mode === "text"
130+ ? "border-b-2 border-purple-600 text-purple-600"
131+ : "text-gray-500 hover:text-gray-700"
132+ } `}
133+ disabled = { isLoading }
134+ >
135+ Текстовое описание
136+ </ button >
137+ < button
138+ onClick = { ( ) => {
139+ setMode ( "songsterr" ) ;
140+ } }
141+ className = { `px-6 py-3 text-sm font-medium transition-colors ${
142+ mode === "songsterr"
143+ ? "border-b-2 border-purple-600 text-purple-600"
144+ : "text-gray-500 hover:text-gray-700"
145+ } `}
146+ disabled = { isLoading }
147+ >
148+ Ссылка Songsterr
149+ </ button >
150+ </ div >
151+ </ div >
152+
67153 < div className = "p-6" >
68- < textarea
69- value = { prompt }
70- onChange = { ( e ) => {
71- setPrompt ( e . target . value ) ;
72- } }
73- onKeyUp = { handleKeyPress }
74- placeholder = "Например: Тяжелый металлический звук с дисторшном и ревербом для соло..."
75- className = "w-full p-4 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent resize-none"
76- rows = { 4 }
77- disabled = { isLoading }
78- />
154+ { mode === "text" ? (
155+ < >
156+ < textarea
157+ value = { prompt }
158+ onChange = { ( e ) => {
159+ setPrompt ( e . target . value ) ;
160+ } }
161+ onKeyUp = { handleKeyPress }
162+ placeholder = "Например: Metallica Enter Sandman Rhythm Guitar Main Riff"
163+ className = "w-full p-4 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent resize-none"
164+ rows = { 4 }
165+ disabled = { isLoading }
166+ />
167+ </ >
168+ ) : (
169+ < >
170+ < div className = "space-y-4" >
171+ < div >
172+ < label className = "block text-sm font-medium text-gray-700 mb-2" >
173+ Ссылка на Songsterr
174+ </ label >
175+ < input
176+ type = "text"
177+ value = { songsterrUrl }
178+ onChange = { ( e ) => {
179+ setSongsterrUrl ( e . target . value ) ;
180+ } }
181+ onKeyUp = { handleKeyPress }
182+ placeholder = "https://www.songsterr.com/a/wsa/..."
183+ className = "w-full p-4 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
184+ disabled = { isLoading }
185+ />
186+ </ div >
187+ < div >
188+ < label className = "block text-sm font-medium text-gray-700 mb-2" >
189+ Тип трека
190+ < span className = "ml-2 text-xs text-gray-500" >
191+ (автоопределение по популярности)
192+ </ span >
193+ </ label >
194+ < select
195+ value = { trackType }
196+ onChange = { ( e ) => {
197+ setTrackType ( e . target . value ) ;
198+ } }
199+ className = "w-full p-4 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
200+ disabled = { isLoading }
201+ >
202+ < option value = "auto" >
203+ 🎯 Автоматически (рекомендуется)
204+ </ option >
205+ < option value = "Rhythm" > Rhythm Guitar</ option >
206+ < option value = "Lead" > Lead Guitar</ option >
207+ < option value = "Solo" > Solo</ option >
208+ </ select >
209+ </ div >
210+ </ div >
211+ </ >
212+ ) }
79213 < div className = "mt-4 flex items-center justify-between" >
80214 < p className = "text-sm text-gray-500" >
81- Нажмите Enter для генерации или используйте кнопку
215+ { mode === "text"
216+ ? "Нажмите Enter для генерации или используйте кнопку"
217+ : "Введите ссылку на табы с Songsterr" }
82218 </ p >
83219 < button
84220 onClick = { ( ) => {
85221 void handleGenerate ( ) ;
86222 } }
87- disabled = { isLoading || ! prompt . trim ( ) }
223+ disabled = { isLoading || isDisabled }
88224 className = { `px-6 py-3 rounded-lg font-medium transition-all ${
89- isLoading || ! prompt . trim ( )
225+ isLoading || isDisabled
90226 ? "bg-gray-300 text-gray-500 cursor-not-allowed"
91227 : "bg-purple-600 text-white hover:bg-purple-700 shadow-lg hover:shadow-xl"
92228 } `}
0 commit comments