issue/6-generacin-de-materias #16
@@ -1,8 +1,12 @@
|
|||||||
{
|
{
|
||||||
"allowScripts": ["npm:deno@2.6.4", "npm:supabase-js@1.0.4", "npm:supabase@2.72.6"],
|
"allowScripts": [
|
||||||
|
"npm:deno@2.6.4",
|
||||||
|
"npm:supabase-js@1.0.4",
|
||||||
|
"npm:supabase@2.72.6"
|
||||||
|
],
|
||||||
"imports": {
|
"imports": {
|
||||||
"@openai/openai": "jsr:@openai/openai@^6.16.0",
|
"@openai/openai": "jsr:@openai/openai@^6.16.0",
|
||||||
"@zod/zod": "jsr:@zod/zod@^4.3.5"
|
"@zod/zod": "jsr:@zod/zod@^4.3.5",
|
||||||
"@david/dax": "jsr:@david/dax@^0.44.2"
|
"@david/dax": "jsr:@david/dax@^0.44.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export const corsHeaders = {
|
export const corsHeaders = {
|
||||||
"Access-Control-Allow-Origin": "*",
|
"Access-Control-Allow-Origin": "*",
|
||||||
"Access-Control-Allow-Headers":
|
"Access-Control-Allow-Headers":
|
||||||
"authorization, x-client-info, apikey, content-type",
|
"authorization, x-client-info, apikey, content-type, x-supabase-client-platform",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -168,8 +168,15 @@ Deno.serve(async (req: Request): Promise<Response> => {
|
|||||||
|
|
||||||
const userPrompt =
|
const userPrompt =
|
||||||
`Genera un borrador completo del PLAN DE ESTUDIOS con base en lo siguiente:
|
`Genera un borrador completo del PLAN DE ESTUDIOS con base en lo siguiente:
|
||||||
- Descripción del enfoque: ${payload.iaConfig.descripcionEnfoque}
|
- Nombre de la institución: Universidad La Salle México
|
||||||
- Notas adicionales: ${payload.iaConfig.notasAdicionales ?? "Ninguna"}`;
|
- Nombre del plan: ${payload.datosBasicos.nombrePlan}
|
||||||
|
- Nivel: ${payload.datosBasicos.nivel}
|
||||||
|
- Tipo de ciclo: ${payload.datosBasicos.tipoCiclo}
|
||||||
|
- Número de ciclos: ${payload.datosBasicos.numCiclos}
|
||||||
|
- Descripción del enfoque académico (sobre el contenido de la respuesta generada): ${payload.iaConfig.descripcionEnfoqueAcademico}
|
||||||
|
- Notas adicionales (sobre el formato de la respuesta generada): ${
|
||||||
|
payload.iaConfig.instruccionesAdicionalesIA ?? "Ninguna"
|
||||||
|
}`;
|
||||||
// Ensure the JSON schema is an object as required by OpenAI types
|
// Ensure the JSON schema is an object as required by OpenAI types
|
||||||
const schemaDef: Record<string, unknown> =
|
const schemaDef: Record<string, unknown> =
|
||||||
typeof estructuraPlan?.definicion === "object" &&
|
typeof estructuraPlan?.definicion === "object" &&
|
||||||
@@ -313,8 +320,10 @@ Deno.serve(async (req: Request): Promise<Response> => {
|
|||||||
repositoriosIds: payload.iaConfig?.repositoriosIds ?? null,
|
repositoriosIds: payload.iaConfig?.repositoriosIds ?? null,
|
||||||
},
|
},
|
||||||
iaConfig: {
|
iaConfig: {
|
||||||
descripcionEnfoque: payload.iaConfig?.descripcionEnfoque ?? null,
|
descripcionEnfoqueAcademico:
|
||||||
notasAdicionales: payload.iaConfig?.notasAdicionales ?? null,
|
payload.iaConfig?.descripcionEnfoqueAcademico ?? null,
|
||||||
|
instruccionesAdicionalesIA:
|
||||||
|
payload.iaConfig?.instruccionesAdicionalesIA ?? null,
|
||||||
usarMCP: Boolean(payload.iaConfig?.usarMCP),
|
usarMCP: Boolean(payload.iaConfig?.usarMCP),
|
||||||
},
|
},
|
||||||
} as unknown as Json,
|
} as unknown as Json,
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ export type AIGeneratePlanInput = {
|
|||||||
estructuraPlanId: string;
|
estructuraPlanId: string;
|
||||||
};
|
};
|
||||||
iaConfig: {
|
iaConfig: {
|
||||||
descripcionEnfoque: string;
|
descripcionEnfoqueAcademico: string;
|
||||||
notasAdicionales?: string;
|
instruccionesAdicionalesIA?: string;
|
||||||
archivosReferencia?: Array<string>;
|
archivosReferencia?: Array<string>;
|
||||||
repositoriosIds?: Array<string>;
|
repositoriosIds?: Array<string>;
|
||||||
usarMCP?: boolean;
|
usarMCP?: boolean;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// Setup type definitions for built-in Supabase Runtime APIs
|
// Setup type definitions for built-in Supabase Runtime APIs
|
||||||
import "@supabase/functions-js/edge-runtime.d.ts";
|
import "@supabase/functions-js/edge-runtime.d.ts";
|
||||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
||||||
|
|
||||||
type WebhookPayload = {
|
type WebhookPayload = {
|
||||||
type: "INSERT" | "UPDATE" | "DELETE";
|
type: "INSERT" | "UPDATE" | "DELETE";
|
||||||
table: string;
|
table: string;
|
||||||
@@ -9,28 +8,23 @@ type WebhookPayload = {
|
|||||||
record: Record<string, unknown> | null;
|
record: Record<string, unknown> | null;
|
||||||
old_record: Record<string, unknown> | null;
|
old_record: Record<string, unknown> | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SUPABASE_URL = Deno.env.get("SUPABASE_URL") ?? "";
|
const SUPABASE_URL = Deno.env.get("SUPABASE_URL") ?? "";
|
||||||
const SUPABASE_SERVICE_ROLE_KEY =
|
const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ??
|
||||||
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "";
|
"";
|
||||||
const OPENAI_API_KEY = Deno.env.get("OPENAI_API_KEY") ?? "";
|
const OPENAI_API_KEY = Deno.env.get("OPENAI_API_KEY") ?? "";
|
||||||
const OPENAI_BASE_URL =
|
const OPENAI_BASE_URL = Deno.env.get("OPENAI_BASE_URL") ??
|
||||||
Deno.env.get("OPENAI_BASE_URL") ?? "https://api.openai.com/v1";
|
"https://api.openai.com/v1";
|
||||||
|
|
||||||
const ALLOWED_SCHEMA = "public";
|
const ALLOWED_SCHEMA = "public";
|
||||||
const ALLOWED_TABLES = new Set(["planes_estudio", "asignaturas"]);
|
const ALLOWED_TABLES = new Set(["planes_estudio", "asignaturas"]);
|
||||||
|
|
||||||
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, {
|
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, {
|
||||||
auth: { persistSession: false },
|
auth: { persistSession: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
function jsonResponse(status: number, body: Record<string, unknown>) {
|
function jsonResponse(status: number, body: Record<string, unknown>) {
|
||||||
return new Response(JSON.stringify(body), {
|
return new Response(JSON.stringify(body), {
|
||||||
status,
|
status,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createConversationId(metadata: Record<string, string>) {
|
async function createConversationId(metadata: Record<string, string>) {
|
||||||
const response = await fetch(`${OPENAI_BASE_URL}/conversations`, {
|
const response = await fetch(`${OPENAI_BASE_URL}/conversations`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -40,61 +34,47 @@ async function createConversationId(metadata: Record<string, string>) {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({ metadata }),
|
body: JSON.stringify({ metadata }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
throw new Error(`OpenAI error: ${response.status} ${errorText}`);
|
throw new Error(`OpenAI error: ${response.status} ${errorText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const conversationId = data?.id as string | undefined;
|
const conversationId = data?.id as string | undefined;
|
||||||
|
|
||||||
if (!conversationId) {
|
if (!conversationId) {
|
||||||
throw new Error("OpenAI response missing conversation id");
|
throw new Error("OpenAI response missing conversation id");
|
||||||
}
|
}
|
||||||
|
|
||||||
return conversationId;
|
return conversationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
Deno.serve(async (req) => {
|
Deno.serve(async (req) => {
|
||||||
if (req.method !== "POST") {
|
if (req.method !== "POST") {
|
||||||
return jsonResponse(405, { error: "Method not allowed" });
|
return jsonResponse(405, { error: "Method not allowed" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SUPABASE_URL || !SUPABASE_SERVICE_ROLE_KEY) {
|
if (!SUPABASE_URL || !SUPABASE_SERVICE_ROLE_KEY) {
|
||||||
return jsonResponse(500, { error: "Supabase env vars missing" });
|
return jsonResponse(500, { error: "Supabase env vars missing" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!OPENAI_API_KEY) {
|
if (!OPENAI_API_KEY) {
|
||||||
return jsonResponse(500, { error: "OPENAI_API_KEY missing" });
|
return jsonResponse(500, { error: "OPENAI_API_KEY missing" });
|
||||||
}
|
}
|
||||||
|
|
||||||
let payload: WebhookPayload;
|
let payload: WebhookPayload;
|
||||||
try {
|
try {
|
||||||
payload = await req.json();
|
payload = await req.json();
|
||||||
} catch {
|
} catch {
|
||||||
return jsonResponse(400, { error: "Invalid JSON body" });
|
return jsonResponse(400, { error: "Invalid JSON body" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { type, table, schema, record } = payload;
|
const { type, table, schema, record } = payload;
|
||||||
|
|
||||||
if (schema !== ALLOWED_SCHEMA || !ALLOWED_TABLES.has(table)) {
|
if (schema !== ALLOWED_SCHEMA || !ALLOWED_TABLES.has(table)) {
|
||||||
return jsonResponse(400, { error: "Table not allowed" });
|
return jsonResponse(400, { error: "Table not allowed" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type !== "INSERT") {
|
if (type !== "INSERT") {
|
||||||
return jsonResponse(200, { status: "ignored", reason: "event_type" });
|
return jsonResponse(200, { status: "ignored", reason: "event_type" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!record || typeof record !== "object") {
|
if (!record || typeof record !== "object") {
|
||||||
return jsonResponse(400, { error: "Missing record" });
|
return jsonResponse(400, { error: "Missing record" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const recordId = record["id"] as string | number | undefined;
|
const recordId = record["id"] as string | number | undefined;
|
||||||
if (!recordId) {
|
if (!recordId) {
|
||||||
return jsonResponse(400, { error: "Record id missing" });
|
return jsonResponse(400, { error: "Record id missing" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingConversationId = record["conversation_id"] as
|
const existingConversationId = record["conversation_id"] as
|
||||||
| string
|
| string
|
||||||
| null
|
| null
|
||||||
@@ -105,7 +85,6 @@ Deno.serve(async (req) => {
|
|||||||
conversation_id: existingConversationId,
|
conversation_id: existingConversationId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let conversationId: string;
|
let conversationId: string;
|
||||||
try {
|
try {
|
||||||
conversationId = await createConversationId({
|
conversationId = await createConversationId({
|
||||||
@@ -115,16 +94,13 @@ Deno.serve(async (req) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return jsonResponse(502, { error: (error as Error).message });
|
return jsonResponse(502, { error: (error as Error).message });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from(table)
|
.from(table)
|
||||||
.update({ conversation_id: conversationId })
|
.update({ conversation_id: conversationId })
|
||||||
.eq("id", recordId);
|
.eq("id", recordId);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return jsonResponse(500, { error: error.message });
|
return jsonResponse(500, { error: error.message });
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonResponse(200, {
|
return jsonResponse(200, {
|
||||||
status: "updated",
|
status: "updated",
|
||||||
table,
|
table,
|
||||||
|
|||||||
Reference in New Issue
Block a user