Versión funcional de creación de plan de estudios con IA
This commit is contained in:
@@ -1,10 +1,5 @@
|
||||
// supabase/functions/ai-structured/index.ts
|
||||
/// <reference lib="deno.window" />
|
||||
import OpenAI from "openai";
|
||||
import type * as OpenAITypes from "openai";
|
||||
|
||||
// Function deprecated. This endpoint was replaced by shared module `_shared/openai-service.ts`.
|
||||
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
||||
import { createClient } from "npm:@supabase/supabase-js@2";
|
||||
|
||||
const json = (body: unknown, status = 200) =>
|
||||
new Response(JSON.stringify(body), {
|
||||
@@ -16,151 +11,14 @@ const json = (body: unknown, status = 200) =>
|
||||
},
|
||||
});
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
Deno.serve((req) => {
|
||||
if (req.method === "OPTIONS") return json({ ok: true });
|
||||
if (req.method !== "POST") return json({ error: "Method not allowed" }, 405);
|
||||
|
||||
console.log("Received request");
|
||||
|
||||
try {
|
||||
const SUPABASE_URL = Deno.env.get("SUPABASE_URL") ?? "";
|
||||
const SUPABASE_SERVICE_ROLE_KEY =
|
||||
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "";
|
||||
const SUPABASE_BUCKET = Deno.env.get("SUPABASE_BUCKET") ?? "ai-storage";
|
||||
const OPENAI_API_KEY = Deno.env.get("OPENAI_API_KEY") ?? "";
|
||||
|
||||
if (!SUPABASE_URL || !SUPABASE_SERVICE_ROLE_KEY || !OPENAI_API_KEY) {
|
||||
return json({ error: "Missing env vars" }, 500);
|
||||
}
|
||||
|
||||
console.log("Env vars loaded");
|
||||
|
||||
type InputForm = {
|
||||
options: OpenAITypes.OpenAI.Responses.ResponseCreateParams;
|
||||
files: File[];
|
||||
};
|
||||
const fd = await req.formData();
|
||||
|
||||
// Asume siempre vienen: options, files[]
|
||||
const optionsRaw = fd.get("options");
|
||||
|
||||
const inputForm: InputForm = {
|
||||
options: typeof optionsRaw === "string"
|
||||
? JSON.parse(optionsRaw)
|
||||
: optionsRaw,
|
||||
|
||||
files: fd.getAll("files").filter((x): x is File => x instanceof File),
|
||||
};
|
||||
console.log(`Parsed form data: ${inputForm.files.length} files`);
|
||||
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY);
|
||||
const openai = new OpenAI({ apiKey: OPENAI_API_KEY });
|
||||
|
||||
console.log("Parsed options");
|
||||
|
||||
// 1) Upload a Supabase Storage
|
||||
const supabase_paths: string[] = [];
|
||||
for (const file of inputForm.files ?? []) {
|
||||
const safeName = file.name
|
||||
.normalize("NFD") // 1. Descompone letras de tildes (í -> i + ´)
|
||||
.replace(/[\u0300-\u036f]/g, "") // 2. Borra las tildes
|
||||
.replace(/[^a-zA-Z0-9.-]/g, "_"); // 3. Reemplaza espacios y símbolos raros por "_"
|
||||
const path = `${crypto.randomUUID()}-${safeName}`;
|
||||
const { data, error } = await supabase.storage
|
||||
.from(SUPABASE_BUCKET)
|
||||
.upload(path, file, {
|
||||
contentType: file.type || "application/octet-stream",
|
||||
upsert: false,
|
||||
/* metadata */
|
||||
});
|
||||
|
||||
if (error) throw new Error(`Supabase upload failed: ${error.message}`);
|
||||
supabase_paths.push(data.path);
|
||||
}
|
||||
|
||||
console.log("Uploaded files to Supabase Storage");
|
||||
|
||||
// 2) Upload a OpenAI Files
|
||||
const openai_file_ids: string[] = [];
|
||||
for (const file of inputForm.files ?? []) {
|
||||
const created = await openai.files.create({
|
||||
file,
|
||||
purpose: "user_data",
|
||||
});
|
||||
openai_file_ids.push(created.id);
|
||||
}
|
||||
|
||||
console.log("Uploaded files to OpenAI Files");
|
||||
|
||||
// 3) Inject file_ids into options.input by appending a new user message
|
||||
const fileParts: OpenAITypes.OpenAI.Responses.ResponseInputFile[] =
|
||||
openai_file_ids.map((id) => ({
|
||||
type: "input_file",
|
||||
file_id: id,
|
||||
}));
|
||||
|
||||
// Asume que SIEMPRE hay input; de todas formas, cae bien si no.
|
||||
const inputArr = Array.isArray(inputForm.options?.input)
|
||||
? inputForm.options.input
|
||||
: [];
|
||||
|
||||
inputArr.push({
|
||||
role: "user",
|
||||
content: [
|
||||
...fileParts,
|
||||
{ type: "input_text", text: "usa estos archivos como referencia" },
|
||||
],
|
||||
});
|
||||
|
||||
inputForm.options.input = inputArr;
|
||||
|
||||
console.log("Prepared OpenAI options with file references");
|
||||
|
||||
// 4) Call Responses API
|
||||
const openai_raw = await openai.responses.create(inputForm.options);
|
||||
|
||||
console.log("Received response from OpenAI Responses API");
|
||||
|
||||
type SuccessResponse = {
|
||||
ok: true;
|
||||
openai_raw: OpenAITypes.OpenAI.Responses.Response;
|
||||
supabase_paths: string[];
|
||||
openai_file_ids: string[];
|
||||
options: unknown;
|
||||
};
|
||||
|
||||
const response: SuccessResponse = {
|
||||
ok: true,
|
||||
openai_raw, // cruda
|
||||
supabase_paths, // rutas en el bucket
|
||||
openai_file_ids, // IDs en OpenAI
|
||||
options: inputForm.options, // opciones usadas
|
||||
};
|
||||
response.openai_raw.output.
|
||||
|
||||
return json(response);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
console.error("Error occurred:", e.message);
|
||||
return json(
|
||||
{
|
||||
ok: false,
|
||||
|
||||
error: e?.message ?? String(e),
|
||||
// si fue Zod, normalmente trae detalles en `issues`
|
||||
issues: (e as any)?.issues,
|
||||
},
|
||||
400,
|
||||
);
|
||||
} else {
|
||||
console.error("Unknown error occurred:", e);
|
||||
return json(
|
||||
{
|
||||
ok: false,
|
||||
error: "An unknown error occurred",
|
||||
},
|
||||
500,
|
||||
);
|
||||
}
|
||||
}
|
||||
return json(
|
||||
{
|
||||
ok: false,
|
||||
error:
|
||||
"This endpoint is deprecated. Use the shared module _shared/openai-service.ts from your functions.",
|
||||
},
|
||||
410,
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user