diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..56edb92 --- /dev/null +++ b/.env.development @@ -0,0 +1,4 @@ +#VITE_API_URL=https://sapient-lab-api-fkadhxgthve3hycv.eastus-01.azurewebsites.net +VITE_API_URL=http://localhost:3000 +API_BLOCKCHAIN=https://backend-blockchain-sapiens-lab.vercel.app +VITE_API_BLOCKCHAIN=https://backend-blockchain-sapiens-lab.vercel.app \ No newline at end of file diff --git a/.env.production b/.env.production index 5ab493c..012d169 100644 --- a/.env.production +++ b/.env.production @@ -1,4 +1,5 @@ # Backend base URL used by Vite production builds. # Set this in CI/CD (or override locally) to the deployed NestJS endpoint. VITE_API_URL=https://sapient-lab-api-fkadhxgthve3hycv.eastus-01.azurewebsites.net -VITE_API_TARGET=https://sapient-lab-api-fkadhxgthve3hycv.eastus-01.azurewebsites.net \ No newline at end of file +VITE_API_TARGET=https://sapient-lab-api-fkadhxgthve3hycv.eastus-01.azurewebsites.net +VITE_API_BLOCKCHAIN=https://backend-blockchain-sapiens-lab.vercel.app diff --git a/src/pages/IntelligentLabNotebook.tsx b/src/pages/IntelligentLabNotebook.tsx index 62855d3..914f837 100644 --- a/src/pages/IntelligentLabNotebook.tsx +++ b/src/pages/IntelligentLabNotebook.tsx @@ -11,6 +11,7 @@ import { FiMic, FiMicOff, FiTrash2, + FiUploadCloud, } from 'react-icons/fi'; import { aiService } from '../services/aiService'; import TaskRecommendation from '../components/TaskRecommendation'; @@ -70,6 +71,8 @@ export default function IntelligentLabNotebook() { const [isSaving, setIsSaving] = useState(false); const [saveStatus, setSaveStatus] = useState<'idle' | 'saved' | 'error'>('idle'); const [noteTitle, setNoteTitle] = useState(''); + const [isUploadingBlockchain, setIsUploadingBlockchain] = useState(false); + const [blockchainStatus, setBlockchainStatus] = useState<'idle' | 'success' | 'error'>('idle'); const scrollRef = useRef(null); const noteTextareaRef = useRef(null); @@ -419,6 +422,66 @@ export default function IntelligentLabNotebook() { await generateSuggestions(noteIdToAnalyze); }; + const handleUploadToBlockchain = async () => { + if (!noteContent.trim() || isUploadingBlockchain) return; + setIsUploadingBlockchain(true); + setBlockchainStatus('idle'); + + try { + // 1. Guardar la nota para asegurar que existe en el backend y tiene ID real + const savedId = await saveNote(noteContent); + if (!savedId) throw new Error('No se pudo guardar la nota antes de mintear'); + + // 2. Obtener los datos reales de la nota desde el backend + const noteResponse = await fetch(`/api/experiment-notes/${savedId}`); + if (!noteResponse.ok) throw new Error(`Error al obtener nota: ${noteResponse.status}`); + const noteData = await noteResponse.json(); + + const title = noteData.title ?? noteTitle.trim() ?? deriveTitle(noteContent); + const description = noteData.content ?? noteContent; + const createdAt = noteData.created_at + ? new Date(noteData.created_at).toISOString().replace('T', ' ').substring(0, 23) + : new Date().toISOString().replace('T', ' ').substring(0, 23); + const publishedAt = new Date().toISOString().replace('T', ' ').substring(0, 23); + const userName = localStorage.getItem('sapientlab_user_name') ?? 'Desconocido'; + + // 3. Construir el payload exacto que espera el endpoint + const payload = { + name: title, + description, + image: 'https://moccasin-magnetic-gopher-766.mypinata.cloud/ipfs/bafybeiagdk4wzi4pz6sbzytf6w2b5kxj6idyex5lsuzkfcu7lngo6rinjm', + attributes: [ + { trait_type: 'ID', value: `EXP-${savedId}` }, + { trait_type: 'Usuario', value: userName }, + { trait_type: 'Creado', value: createdAt }, + { trait_type: 'publicado', value: publishedAt }, + ], + }; + + // 4. Subir a blockchain + const blockchainUrl = import.meta.env.VITE_API_BLOCKCHAIN ?? 'https://backend-blockchain-sapiens-lab.vercel.app'; + const response = await fetch(`${blockchainUrl}/pinata/upload-json`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + const errData = await response.json().catch(() => null); + throw new Error(errData?.message ?? `Error ${response.status}`); + } + + await response.json(); + setBlockchainStatus('success'); + } catch (error) { + console.error('Error subiendo a blockchain:', error); + setBlockchainStatus('error'); + setTimeout(() => setBlockchainStatus('idle'), 4000); + } finally { + setIsUploadingBlockchain(false); + } + }; + const generateSuggestions = async (noteId: number) => { setIsGeneratingSuggestions(true); try { @@ -749,12 +812,17 @@ ${sugg.safetyWarnings.length > 0 ? `### Advertencias de Seguridad\n${sugg.safety placeholder="Escribe tus notas del experimento aquí. Luego usa 'Analizar texto' para pedir sugerencias a la IA..." className="flex-1 p-4 rounded-xl resize-none font-mono text-sm focus:outline-none focus:ring-2 focus:ring-accent/50 bg-[#0a0f1c] border border-accent/20 text-slate-200 placeholder:text-slate-600 transition-all duration-300" /> -
+
{isGeneratingSuggestions && ( Analizando... )} + {isUploadingBlockchain && ( + + Subiendo a blockchain... + + )} +
+ + {/* Banner Auditar Nota en Blockchain */} + {blockchainStatus === 'success' && ( +
+
+ +
+
+

Nota minteada en Somnia Network

+ + Auditar Notas → + +
+ +
+ )}