mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-18 14:27:51 +00:00
Compare commits
69 Commits
618ecaf040
...
de28dea3b5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de28dea3b5 | ||
|
|
4765be66da | ||
|
|
c562ae570a | ||
|
|
f4965db3e6 | ||
|
|
933358483e | ||
|
|
66d401f98f | ||
|
|
a3b522d283 | ||
|
|
5812d46049 | ||
|
|
007441c285 | ||
|
|
c776efaec9 | ||
|
|
6cf273126e | ||
|
|
ec97f640f9 | ||
|
|
a8c332d442 | ||
|
|
4d09386d96 | ||
|
|
cc23c83c05 | ||
|
|
521a99a5c2 | ||
|
|
563a62a517 | ||
|
|
3ef8c99062 | ||
|
|
d5c75ab6c2 | ||
|
|
28fa4d70e6 | ||
|
|
02119a62d1 | ||
|
|
7087a87ece | ||
|
|
fbeeace8bb | ||
|
|
961fce03f6 | ||
|
|
8af9950ed7 | ||
|
|
7e3b4551ec | ||
|
|
03732de610 | ||
|
|
3701e692f1 | ||
|
|
4f3b80246f | ||
|
|
0b8c050bd7 | ||
|
|
1a3a603ff6 | ||
|
|
0661f2c225 | ||
|
|
6531a9282c | ||
|
|
1132f7438d | ||
|
|
797967ca5b | ||
|
|
6f8e890e86 | ||
|
|
f70585e9c1 | ||
|
|
5573274ad4 | ||
|
|
9ecf46a9ac | ||
|
|
6e7c85e853 | ||
|
|
1e181785b4 | ||
|
|
eb77476d51 | ||
|
|
8e8936e9f4 | ||
|
|
dea81a5711 | ||
|
|
89c325cc7c | ||
|
|
7430ae15a8 | ||
|
|
c8420421eb | ||
|
|
441b55535e | ||
|
|
4b431358e0 | ||
|
|
c0aa725fa6 | ||
|
|
fca293c4fc | ||
|
|
beef3da647 | ||
|
|
5952d83ec8 | ||
|
|
f1a7cd8730 | ||
|
|
e9e72677a4 | ||
|
|
fd734a5c26 | ||
|
|
70953ab57a | ||
|
|
fd50d59d3c | ||
|
|
d8c665d48e | ||
|
|
39bbc2c827 | ||
|
|
3176e95a75 | ||
|
|
6f03e72a22 | ||
|
|
abf0033590 | ||
|
|
4cc6ab641e | ||
|
|
5193ba95f4 | ||
|
|
b7d30fdc06 | ||
|
|
6afb728dce | ||
|
|
543ed7532b | ||
|
|
677ee422c4 |
24
CHANGELOG.md
24
CHANGELOG.md
@ -1,19 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [1.1.0] - 2024-01-27
|
||||
|
||||
### Adicionado
|
||||
- Função de processamento de áudio (process-audio)
|
||||
- Integração com OpenAI Whisper para transcrição
|
||||
- Análise de leitura usando GPT-4
|
||||
- Database Trigger para processamento automático de gravações
|
||||
|
||||
### Técnico
|
||||
- Implementação de Edge Function no Supabase
|
||||
- Configuração de ambiente de desenvolvimento com Docker
|
||||
- Integração com banco de dados para story_recordings
|
||||
- Sistema de análise de métricas de leitura
|
||||
## [1.1.0] - 2024-03-21
|
||||
|
||||
### Modificado
|
||||
- Estrutura de armazenamento de gravações
|
||||
- Fluxo de processamento de áudio automatizado
|
||||
- Melhorado o processo de upload de áudio para evitar colisões de arquivos e garantir integridade dos dados
|
||||
- Implementado processamento assíncrono de áudio via Edge Function
|
||||
|
||||
### Técnico
|
||||
- Adicionado UUID para identificação única de arquivos de áudio
|
||||
- Implementada transação atômica para upload de áudio
|
||||
- Integrada chamada assíncrona para processamento de áudio
|
||||
- Melhorado tratamento de erros no processo de upload
|
||||
24
package-lock.json
generated
24
package-lock.json
generated
@ -24,12 +24,14 @@
|
||||
"react-router-dom": "^6.28.0",
|
||||
"recharts": "^2.15.0",
|
||||
"resend": "^3.2.0",
|
||||
"tailwind-merge": "^2.5.5"
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"uuid": "^11.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.1",
|
||||
"@types/react": "^18.3.17",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.9.1",
|
||||
@ -2473,6 +2475,13 @@
|
||||
"source-map": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
|
||||
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/webpack": {
|
||||
"version": "4.41.40",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.40.tgz",
|
||||
@ -6356,6 +6365,19 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz",
|
||||
"integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/victory-vendor": {
|
||||
"version": "36.9.2",
|
||||
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
|
||||
|
||||
@ -31,12 +31,14 @@
|
||||
"react-router-dom": "^6.28.0",
|
||||
"recharts": "^2.15.0",
|
||||
"resend": "^3.2.0",
|
||||
"tailwind-merge": "^2.5.5"
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"uuid": "^11.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.1",
|
||||
"@types/react": "^18.3.17",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.9.1",
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { Mic, Square, Loader, Play, Upload } from 'lucide-react';
|
||||
import { supabase } from '../../lib/supabase';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
interface AudioRecorderProps {
|
||||
storyId: string;
|
||||
@ -51,6 +52,29 @@ export function AudioRecorder({ storyId, studentId, onAudioUploaded }: AudioReco
|
||||
}
|
||||
};
|
||||
|
||||
const triggerAudioProcessing = async (recordingData: {
|
||||
id: string;
|
||||
story_id: string;
|
||||
student_id: string;
|
||||
audio_url: string;
|
||||
status: string;
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
const { error } = await supabase.functions.invoke('process-audio', {
|
||||
body: {
|
||||
record: recordingData
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('Erro ao iniciar processamento:', error);
|
||||
// Não vamos tratar o erro aqui pois o processamento é assíncrono
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Erro ao chamar função de processamento:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const uploadAudio = async () => {
|
||||
if (!audioBlob) return;
|
||||
|
||||
@ -63,52 +87,75 @@ export function AudioRecorder({ storyId, studentId, onAudioUploaded }: AudioReco
|
||||
setIsUploading(true);
|
||||
setError(null);
|
||||
|
||||
// Gerar um UUID único para o arquivo
|
||||
const fileId = uuidv4();
|
||||
const filePath = `${studentId}/${storyId}/${fileId}.webm`;
|
||||
|
||||
try {
|
||||
// 1. Primeiro criar o registro no banco
|
||||
// Iniciar uma transação
|
||||
const { data: recordData, error: recordError } = await supabase
|
||||
.from('story_recordings')
|
||||
.insert({
|
||||
id: fileId, // Usar o mesmo UUID como ID do registro
|
||||
story_id: storyId,
|
||||
student_id: studentId,
|
||||
status: 'pending_analysis',
|
||||
status: 'uploading', // Status inicial
|
||||
created_at: new Date().toISOString()
|
||||
})
|
||||
.select()
|
||||
.select('id')
|
||||
.single();
|
||||
|
||||
if (recordError) throw recordError;
|
||||
if (!recordData) throw new Error('Registro não criado');
|
||||
|
||||
// 2. Upload do arquivo usando o ID do registro
|
||||
const filePath = `${studentId}/${storyId}/${recordData.id}.webm`;
|
||||
|
||||
// Upload do arquivo
|
||||
const { error: uploadError } = await supabase.storage
|
||||
.from('recordings') // Mudado para audio-uploads para manter consistência
|
||||
.from('recordings')
|
||||
.upload(filePath, audioBlob, {
|
||||
contentType: 'audio/webm',
|
||||
cacheControl: '3600'
|
||||
cacheControl: '3600',
|
||||
upsert: false
|
||||
});
|
||||
|
||||
if (uploadError) {
|
||||
// Limpar registro se upload falhar
|
||||
await supabase.from('story_recordings').delete().eq('id', recordData.id);
|
||||
// Se o upload falhar, remover o registro do banco
|
||||
await supabase
|
||||
.from('story_recordings')
|
||||
.delete()
|
||||
.eq('id', fileId);
|
||||
throw uploadError;
|
||||
}
|
||||
|
||||
// 3. Obter URL pública
|
||||
// Obter URL pública
|
||||
const { data: { publicUrl } } = supabase.storage
|
||||
.from('recordings')
|
||||
.getPublicUrl(filePath);
|
||||
|
||||
// 4. Atualizar registro com URL
|
||||
// Atualizar o registro com a URL e status
|
||||
const { error: updateError } = await supabase
|
||||
.from('story_recordings')
|
||||
.update({
|
||||
audio_url: publicUrl
|
||||
audio_url: publicUrl,
|
||||
status: 'pending_analysis'
|
||||
})
|
||||
.eq('id', recordData.id);
|
||||
.eq('id', fileId);
|
||||
|
||||
if (updateError) throw updateError;
|
||||
if (updateError) {
|
||||
// Se a atualização falhar, limpar tudo
|
||||
await Promise.all([
|
||||
supabase.storage.from('recordings').remove([filePath]),
|
||||
supabase.from('story_recordings').delete().eq('id', fileId)
|
||||
]);
|
||||
throw updateError;
|
||||
}
|
||||
|
||||
// Disparar o processamento de forma assíncrona
|
||||
triggerAudioProcessing({
|
||||
id: fileId,
|
||||
story_id: storyId,
|
||||
student_id: studentId,
|
||||
audio_url: publicUrl,
|
||||
status: 'pending_analysis'
|
||||
}).catch(console.error); // Capturar erros mas não esperar pela conclusão
|
||||
|
||||
onAudioUploaded(publicUrl);
|
||||
setAudioBlob(null);
|
||||
|
||||
49
supabase/migrations/XXX_create_audio_processing_trigger.sql
Normal file
49
supabase/migrations/XXX_create_audio_processing_trigger.sql
Normal file
@ -0,0 +1,49 @@
|
||||
-- Criar a trigger function
|
||||
create function handle_new_recording()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
security definer
|
||||
as $$
|
||||
declare
|
||||
response json;
|
||||
begin
|
||||
-- Apenas processa registros com status pending_analysis
|
||||
if NEW.status = 'pending_analysis' then
|
||||
-- Chama a Edge Function
|
||||
select
|
||||
content into response
|
||||
from
|
||||
http((
|
||||
'POST',
|
||||
current_setting('app.settings.edge_function_url') || '/process-audio',
|
||||
ARRAY[
|
||||
('Authorization', 'Bearer ' || current_setting('app.settings.service_role_key'))::http_header,
|
||||
('Content-Type', 'application/json')::http_header
|
||||
],
|
||||
'application/json',
|
||||
json_build_object(
|
||||
'record', json_build_object(
|
||||
'id', NEW.id,
|
||||
'story_id', NEW.story_id,
|
||||
'student_id', NEW.student_id,
|
||||
'audio_url', NEW.audio_url,
|
||||
'status', NEW.status
|
||||
)
|
||||
)
|
||||
)::http_request);
|
||||
end if;
|
||||
|
||||
return NEW;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- Configurar as variáveis de ambiente
|
||||
select set_config('app.settings.edge_function_url', 'https://bsjlbnyslxzsdwxvkaap.supabase.co/functions/v1', false);
|
||||
select set_config('app.settings.service_role_key', 'seu_service_role_key', false);
|
||||
|
||||
-- Criar a trigger
|
||||
create trigger process_new_recording
|
||||
after insert or update
|
||||
on story_recordings
|
||||
for each row
|
||||
execute function handle_new_recording();
|
||||
Loading…
Reference in New Issue
Block a user