Merge pull request 'Se genera el contenido temático con IA' (#32) from issue/31-generacin-de-columnas-de-tabla-asignaturas-con-ia into main
Reviewed-on: AlexRG/genesis-2#32
This commit was merged in pull request #32.
This commit is contained in:
@@ -460,6 +460,7 @@ export type Database = {
|
|||||||
estado: Database["public"]["Enums"]["estado_conversacion"]
|
estado: Database["public"]["Enums"]["estado_conversacion"]
|
||||||
id: string
|
id: string
|
||||||
intento_archivado: number
|
intento_archivado: number
|
||||||
|
nombre: string | null
|
||||||
openai_conversation_id: string
|
openai_conversation_id: string
|
||||||
plan_estudio_id: string
|
plan_estudio_id: string
|
||||||
}
|
}
|
||||||
@@ -472,6 +473,7 @@ export type Database = {
|
|||||||
estado?: Database["public"]["Enums"]["estado_conversacion"]
|
estado?: Database["public"]["Enums"]["estado_conversacion"]
|
||||||
id?: string
|
id?: string
|
||||||
intento_archivado?: number
|
intento_archivado?: number
|
||||||
|
nombre?: string | null
|
||||||
openai_conversation_id: string
|
openai_conversation_id: string
|
||||||
plan_estudio_id: string
|
plan_estudio_id: string
|
||||||
}
|
}
|
||||||
@@ -484,6 +486,7 @@ export type Database = {
|
|||||||
estado?: Database["public"]["Enums"]["estado_conversacion"]
|
estado?: Database["public"]["Enums"]["estado_conversacion"]
|
||||||
id?: string
|
id?: string
|
||||||
intento_archivado?: number
|
intento_archivado?: number
|
||||||
|
nombre?: string | null
|
||||||
openai_conversation_id?: string
|
openai_conversation_id?: string
|
||||||
plan_estudio_id?: string
|
plan_estudio_id?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import {
|
|||||||
type StructuredResponseOptions,
|
type StructuredResponseOptions,
|
||||||
} from "../_shared/openai-service.ts";
|
} from "../_shared/openai-service.ts";
|
||||||
|
|
||||||
addEventListener("beforeunload", (ev: any) => {
|
type BeforeUnloadWithDetail = Event & { detail?: { reason?: unknown } };
|
||||||
|
|
||||||
|
addEventListener("beforeunload", (ev: BeforeUnloadWithDetail) => {
|
||||||
console.error(
|
console.error(
|
||||||
"ALERTA: La función se va a apagar. Razón:",
|
"ALERTA: La función se va a apagar. Razón:",
|
||||||
ev?.detail?.reason,
|
ev?.detail?.reason,
|
||||||
@@ -67,6 +69,55 @@ const JsonUpdateSchema = z
|
|||||||
|
|
||||||
type EdgeAIGenerateSubjectJsonUpdateInput = z.infer<typeof JsonUpdateSchema>;
|
type EdgeAIGenerateSubjectJsonUpdateInput = z.infer<typeof JsonUpdateSchema>;
|
||||||
|
|
||||||
|
function withColumnDefsAndRefs(
|
||||||
|
schemaDef: Record<string, unknown>,
|
||||||
|
): Record<string, unknown> {
|
||||||
|
const nextSchema: Record<string, unknown> = {
|
||||||
|
...schemaDef,
|
||||||
|
$defs: definicionesDeEstructurasDeColumnas,
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = nextSchema["properties"];
|
||||||
|
if (!props || typeof props !== "object" || Array.isArray(props)) {
|
||||||
|
return nextSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextProps: Record<string, unknown> = {
|
||||||
|
...props as Record<string, unknown>,
|
||||||
|
};
|
||||||
|
for (const [key, value] of Object.entries(nextProps)) {
|
||||||
|
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
||||||
|
const xColumn = (value as Record<string, unknown>)["x-column"];
|
||||||
|
if (typeof xColumn !== "string" || !xColumn.length) continue;
|
||||||
|
nextProps[key] = { $ref: `#/$defs/${xColumn}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSchema["properties"] = nextProps;
|
||||||
|
return nextSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitAiOutputStringsAndColumns(
|
||||||
|
aiOutput: unknown,
|
||||||
|
): { aiOutputJson: Json; columnasGeneradas: Record<string, unknown> } {
|
||||||
|
if (!aiOutput || typeof aiOutput !== "object" || Array.isArray(aiOutput)) {
|
||||||
|
return { aiOutputJson: aiOutput as unknown as Json, columnasGeneradas: {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = aiOutput as Record<string, unknown>;
|
||||||
|
const stringsOnly: Record<string, string> = {};
|
||||||
|
const columnasGeneradas: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(record)) {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
stringsOnly[key] = value;
|
||||||
|
} else {
|
||||||
|
columnasGeneradas[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { aiOutputJson: stringsOnly as unknown as Json, columnasGeneradas };
|
||||||
|
}
|
||||||
|
|
||||||
function formatZodIssues(issues: z.ZodIssue[]): string {
|
function formatZodIssues(issues: z.ZodIssue[]): string {
|
||||||
return issues
|
return issues
|
||||||
.map((issue, i) => {
|
.map((issue, i) => {
|
||||||
@@ -313,7 +364,11 @@ Deno.serve(async (req: Request): Promise<Response> => {
|
|||||||
}\n` +
|
}\n` +
|
||||||
`- Descripción del enfoque académico (sobre el contenido de la respuesta generada): ${
|
`- Descripción del enfoque académico (sobre el contenido de la respuesta generada): ${
|
||||||
payload.descripcionEnfoqueAcademico ?? "(ninguna)"
|
payload.descripcionEnfoqueAcademico ?? "(ninguna)"
|
||||||
}`;
|
}\n\n` +
|
||||||
|
`REGLA ESTRICTA MATEMÁTICA:\n` +
|
||||||
|
`Si generas el 'contenido_tematico', la suma total de las 'horasEstimadas' de todos los temas en todas las unidades DEBE coincidir exactamente con el total de Horas académicas indicadas arriba (${
|
||||||
|
resolved.horas_academicas ?? 0
|
||||||
|
}). No te pases ni te falten horas.`;
|
||||||
|
|
||||||
const schemaDef: Record<string, unknown> =
|
const schemaDef: Record<string, unknown> =
|
||||||
typeof estructura?.definicion === "object" &&
|
typeof estructura?.definicion === "object" &&
|
||||||
@@ -321,6 +376,8 @@ Deno.serve(async (req: Request): Promise<Response> => {
|
|||||||
? (estructura.definicion as Record<string, unknown>)
|
? (estructura.definicion as Record<string, unknown>)
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
|
const schemaForAI = withColumnDefsAndRefs(schemaDef);
|
||||||
|
|
||||||
const aiStructuredPayload: StructuredResponseOptions = {
|
const aiStructuredPayload: StructuredResponseOptions = {
|
||||||
model: AI_GENERATE_SUBJECT_UPDATE_MODELO,
|
model: AI_GENERATE_SUBJECT_UPDATE_MODELO,
|
||||||
input: [
|
input: [
|
||||||
@@ -331,7 +388,7 @@ Deno.serve(async (req: Request): Promise<Response> => {
|
|||||||
format: {
|
format: {
|
||||||
type: "json_schema",
|
type: "json_schema",
|
||||||
name: "asignatura_contenido_tematico",
|
name: "asignatura_contenido_tematico",
|
||||||
schema: schemaDef,
|
schema: schemaForAI,
|
||||||
strict: true,
|
strict: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -380,7 +437,8 @@ Deno.serve(async (req: Request): Promise<Response> => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const aiOutputJson: Json = aiOutput as unknown as Json;
|
const { aiOutputJson, columnasGeneradas } =
|
||||||
|
splitAiOutputStringsAndColumns(aiOutput);
|
||||||
|
|
||||||
const updatePatch: Database["public"]["Tables"]["asignaturas"]["Update"] =
|
const updatePatch: Database["public"]["Tables"]["asignaturas"]["Update"] =
|
||||||
{
|
{
|
||||||
@@ -389,6 +447,17 @@ Deno.serve(async (req: Request): Promise<Response> => {
|
|||||||
estado: "borrador",
|
estado: "borrador",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for (const value of Object.values(columnasGeneradas)) {
|
||||||
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const xColumn = (value as Record<string, unknown>)["x-column"];
|
||||||
|
const xDef = (value as Record<string, unknown>)["x-definicion"];
|
||||||
|
if (typeof xColumn !== "string" || !xColumn.length) continue;
|
||||||
|
(updatePatch as unknown as Record<string, unknown>)[xColumn] =
|
||||||
|
xDef as unknown as Json;
|
||||||
|
}
|
||||||
|
|
||||||
// Apply only provided JSON fields (do not overwrite if missing)
|
// Apply only provided JSON fields (do not overwrite if missing)
|
||||||
if (payload.plan_estudio_id !== undefined) {
|
if (payload.plan_estudio_id !== undefined) {
|
||||||
updatePatch.plan_estudio_id = payload.plan_estudio_id;
|
updatePatch.plan_estudio_id = payload.plan_estudio_id;
|
||||||
@@ -561,6 +630,8 @@ Deno.serve(async (req: Request): Promise<Response> => {
|
|||||||
? (estructura.definicion as Record<string, unknown>)
|
? (estructura.definicion as Record<string, unknown>)
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
|
const schemaForAI = withColumnDefsAndRefs(schemaDef);
|
||||||
|
|
||||||
const aiStructuredPayload: StructuredResponseOptions = {
|
const aiStructuredPayload: StructuredResponseOptions = {
|
||||||
model: AI_GENERATE_SUBJECT_INSERT_MODELO,
|
model: AI_GENERATE_SUBJECT_INSERT_MODELO,
|
||||||
input: [
|
input: [
|
||||||
@@ -571,7 +642,7 @@ Deno.serve(async (req: Request): Promise<Response> => {
|
|||||||
format: {
|
format: {
|
||||||
type: "json_schema",
|
type: "json_schema",
|
||||||
name: "asignatura_contenido_tematico",
|
name: "asignatura_contenido_tematico",
|
||||||
schema: schemaDef,
|
schema: schemaForAI,
|
||||||
strict: true,
|
strict: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -623,7 +694,9 @@ Deno.serve(async (req: Request): Promise<Response> => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const aiOutputJson: Json = aiOutput as unknown as Json;
|
const { aiOutputJson, columnasGeneradas } = splitAiOutputStringsAndColumns(
|
||||||
|
aiOutput,
|
||||||
|
);
|
||||||
|
|
||||||
const asignaturaInsert:
|
const asignaturaInsert:
|
||||||
Database["public"]["Tables"]["asignaturas"]["Insert"] = {
|
Database["public"]["Tables"]["asignaturas"]["Insert"] = {
|
||||||
@@ -661,6 +734,15 @@ Deno.serve(async (req: Request): Promise<Response> => {
|
|||||||
} as unknown as Json,
|
} as unknown as Json,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for (const value of Object.values(columnasGeneradas)) {
|
||||||
|
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
||||||
|
const xColumn = (value as Record<string, unknown>)["x-column"];
|
||||||
|
const xDef = (value as Record<string, unknown>)["x-definicion"];
|
||||||
|
if (typeof xColumn !== "string" || !xColumn.length) continue;
|
||||||
|
(asignaturaInsert as unknown as Record<string, unknown>)[xColumn] =
|
||||||
|
xDef as unknown as Json;
|
||||||
|
}
|
||||||
|
|
||||||
const { data: asignatura, error: asignaturaError } = await supabaseService
|
const { data: asignatura, error: asignaturaError } = await supabaseService
|
||||||
.from("asignaturas")
|
.from("asignaturas")
|
||||||
.insert(asignaturaInsert)
|
.insert(asignaturaInsert)
|
||||||
@@ -798,3 +880,63 @@ function parseAndValidate(
|
|||||||
|
|
||||||
return { success: true, data: result.data };
|
return { success: true, data: result.data };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const definicionesDeEstructurasDeColumnas = {
|
||||||
|
contenido_tematico: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"x-column": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"contenido_tematico",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"x-definicion": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"unidad": {
|
||||||
|
"type": "integer",
|
||||||
|
},
|
||||||
|
"titulo": {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"temas": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"nombre": {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"horasEstimadas": {
|
||||||
|
"type": "integer",
|
||||||
|
"description":
|
||||||
|
"Horas del tema. La suma de todas las horasEstimadas debe ser igual a las horas académicas del prompt.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"nombre",
|
||||||
|
"horasEstimadas",
|
||||||
|
],
|
||||||
|
"additionalProperties": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"unidad",
|
||||||
|
"titulo",
|
||||||
|
"temas",
|
||||||
|
],
|
||||||
|
"additionalProperties": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"x-column",
|
||||||
|
"x-definicion",
|
||||||
|
],
|
||||||
|
"additionalProperties": false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user