alpha01 filetti: web app, crossword service and tor batch
This commit is contained in:
338
webapp/lib/i18n.ts
Normal file
338
webapp/lib/i18n.ts
Normal file
@@ -0,0 +1,338 @@
|
||||
export const LOCALES = ["it", "en", "es"] as const;
|
||||
export type Locale = (typeof LOCALES)[number];
|
||||
|
||||
export function isLocale(value: string): value is Locale {
|
||||
return LOCALES.includes(value as Locale);
|
||||
}
|
||||
|
||||
type Dictionary = {
|
||||
home: {
|
||||
badge: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
requestTitle: string;
|
||||
requestText: string;
|
||||
structureTitle: string;
|
||||
structureText: string;
|
||||
structureChips: string[];
|
||||
demoTitle: string;
|
||||
openDemo: string;
|
||||
openConfig: string;
|
||||
kpis: {
|
||||
words: string;
|
||||
intersections: string;
|
||||
rows: string;
|
||||
cols: string;
|
||||
};
|
||||
};
|
||||
newPage: {
|
||||
badge: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
};
|
||||
form: {
|
||||
topic: string;
|
||||
difficulty: string;
|
||||
initialWords: string;
|
||||
themedFillCount: string;
|
||||
targetEmptyRatio: string;
|
||||
seed: string;
|
||||
lexiconFile: string;
|
||||
submit: string;
|
||||
submitting: string;
|
||||
note: string;
|
||||
};
|
||||
play: {
|
||||
badge: string;
|
||||
subtitle: string;
|
||||
loading: string;
|
||||
errorPrefix: string;
|
||||
overviewTitle: string;
|
||||
boardTitle: string;
|
||||
cluesTitle: string;
|
||||
instructions: string;
|
||||
actions: {
|
||||
newCrossword: string;
|
||||
viewSolution: string;
|
||||
};
|
||||
stats: {
|
||||
words: string;
|
||||
intersections: string;
|
||||
filledCells: string;
|
||||
topicWords: string;
|
||||
};
|
||||
};
|
||||
solution: {
|
||||
badge: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
solvedGridTitle: string;
|
||||
answersTitle: string;
|
||||
backToGame: string;
|
||||
answer: string;
|
||||
clue: string;
|
||||
};
|
||||
player: {
|
||||
topic: string;
|
||||
difficulty: string;
|
||||
lexicon: string;
|
||||
};
|
||||
clues: {
|
||||
across: string;
|
||||
down: string;
|
||||
};
|
||||
};
|
||||
|
||||
const dictionaries: Record<Locale, Dictionary> = {
|
||||
it: {
|
||||
home: {
|
||||
badge: "Alpha 01 Backoffice + Web UI",
|
||||
title: "Cruciverba su richiesta, pronti per essere giocati.",
|
||||
subtitle:
|
||||
"Questa prima interfaccia Next.js separa nettamente il motore Python dal frontend: il sito invia una richiesta JSON, il backend genera il cruciverba e la pagina finale lo rende giocabile in modo interattivo.",
|
||||
requestTitle: "Nuova richiesta",
|
||||
requestText:
|
||||
"Qui l'utente configurerà il cruciverba. Per ora il form prepara già la struttura della richiesta e apre la pagina del gioco reale.",
|
||||
structureTitle: "Perche questa struttura",
|
||||
structureText:
|
||||
"Il frontend non deve conoscere i dettagli dell'algoritmo: riceve solo il JSON finale del cruciverba e lo trasforma in esperienza giocabile, stampabile e riapribile.",
|
||||
structureChips: ["Next.js UI", "Backend JSON", "Motore Python separato", "PDF e mobile-ready"],
|
||||
demoTitle: "Stato demo",
|
||||
openDemo: "Apri demo giocabile",
|
||||
openConfig: "Vai alla configurazione",
|
||||
kpis: {
|
||||
words: "Parole",
|
||||
intersections: "Intersezioni",
|
||||
rows: "Righe",
|
||||
cols: "Colonne",
|
||||
},
|
||||
},
|
||||
newPage: {
|
||||
badge: "Configurazione",
|
||||
title: "Chiedi al motore un nuovo cruciverba.",
|
||||
subtitle:
|
||||
"Questa pagina e il punto naturale da cui il sito invierà al backend la request JSON del contratto che abbiamo definito.",
|
||||
},
|
||||
form: {
|
||||
topic: "Topic",
|
||||
difficulty: "Difficolta",
|
||||
initialWords: "Parole seme",
|
||||
themedFillCount: "Parole filler in tema",
|
||||
targetEmptyRatio: "Rapporto vuoti",
|
||||
seed: "Seed",
|
||||
lexiconFile: "Lessico runtime",
|
||||
submit: "Genera cruciverba",
|
||||
submitting: "Preparazione...",
|
||||
note: "La richiesta ora chiama davvero il motore Python e apre il cruciverba generato.",
|
||||
},
|
||||
play: {
|
||||
badge: "Cruciverba interattivo",
|
||||
subtitle: "Questa pagina carica il JSON reale generato dal motore Python del backoffice.",
|
||||
loading: "Generazione in corso, sto caricando il cruciverba...",
|
||||
errorPrefix: "Errore di caricamento",
|
||||
overviewTitle: "Schema pronto per essere giocato",
|
||||
boardTitle: "Griglia di gioco",
|
||||
cluesTitle: "Definizioni",
|
||||
instructions:
|
||||
"Clicca una casella, poi scrivi da tastiera. Le definizioni restano leggibili nella colonna laterale.",
|
||||
actions: {
|
||||
newCrossword: "Nuovo cruciverba",
|
||||
viewSolution: "Vedi soluzioni",
|
||||
},
|
||||
stats: {
|
||||
words: "Parole",
|
||||
intersections: "Intersezioni",
|
||||
filledCells: "Caselle piene",
|
||||
topicWords: "Parole in tema",
|
||||
},
|
||||
},
|
||||
solution: {
|
||||
badge: "Pagina soluzioni",
|
||||
title: "Controllo finale del cruciverba",
|
||||
subtitle:
|
||||
"Qui trovi la griglia compilata e l'elenco completo delle risposte collegate alle definizioni.",
|
||||
solvedGridTitle: "Griglia risolta",
|
||||
answersTitle: "Elenco soluzioni",
|
||||
backToGame: "Torna al gioco",
|
||||
answer: "Soluzione",
|
||||
clue: "Definizione",
|
||||
},
|
||||
player: {
|
||||
topic: "Topic",
|
||||
difficulty: "Difficolta",
|
||||
lexicon: "Lessico",
|
||||
},
|
||||
clues: {
|
||||
across: "Orizzontali",
|
||||
down: "Verticali",
|
||||
},
|
||||
},
|
||||
en: {
|
||||
home: {
|
||||
badge: "Alpha 01 Backoffice + Web UI",
|
||||
title: "Crosswords on demand, ready to be played.",
|
||||
subtitle:
|
||||
"This first Next.js interface clearly separates the Python engine from the frontend: the site sends a JSON request, the backend generates the crossword, and the final page turns it into an interactive experience.",
|
||||
requestTitle: "New request",
|
||||
requestText:
|
||||
"This is where the user configures the crossword. The form already prepares the request structure and opens the real game page.",
|
||||
structureTitle: "Why this structure",
|
||||
structureText:
|
||||
"The frontend should not know the details of the algorithm: it only receives the final crossword JSON and turns it into a playable, printable and reusable experience.",
|
||||
structureChips: ["Next.js UI", "JSON backend", "Separate Python engine", "PDF and mobile-ready"],
|
||||
demoTitle: "Demo status",
|
||||
openDemo: "Open playable demo",
|
||||
openConfig: "Go to configuration",
|
||||
kpis: {
|
||||
words: "Words",
|
||||
intersections: "Intersections",
|
||||
rows: "Rows",
|
||||
cols: "Columns",
|
||||
},
|
||||
},
|
||||
newPage: {
|
||||
badge: "Configuration",
|
||||
title: "Ask the engine for a new crossword.",
|
||||
subtitle:
|
||||
"This page is the natural place from which the site sends the JSON request defined in our contract to the backend.",
|
||||
},
|
||||
form: {
|
||||
topic: "Topic",
|
||||
difficulty: "Difficulty",
|
||||
initialWords: "Seed words",
|
||||
themedFillCount: "Themed filler words",
|
||||
targetEmptyRatio: "Empty ratio",
|
||||
seed: "Seed",
|
||||
lexiconFile: "Runtime lexicon",
|
||||
submit: "Generate crossword",
|
||||
submitting: "Preparing...",
|
||||
note: "The request now calls the real Python engine and opens the generated crossword.",
|
||||
},
|
||||
play: {
|
||||
badge: "Interactive crossword",
|
||||
subtitle: "This page loads the real JSON generated by the Python backoffice engine.",
|
||||
loading: "Generation in progress, loading crossword...",
|
||||
errorPrefix: "Loading error",
|
||||
overviewTitle: "Crossword ready to be played",
|
||||
boardTitle: "Game grid",
|
||||
cluesTitle: "Clues",
|
||||
instructions: "Click a cell, then type from the keyboard. The clues stay readable in the side panel.",
|
||||
actions: {
|
||||
newCrossword: "New crossword",
|
||||
viewSolution: "View solutions",
|
||||
},
|
||||
stats: {
|
||||
words: "Words",
|
||||
intersections: "Intersections",
|
||||
filledCells: "Filled cells",
|
||||
topicWords: "Theme words",
|
||||
},
|
||||
},
|
||||
solution: {
|
||||
badge: "Solutions page",
|
||||
title: "Final crossword check",
|
||||
subtitle: "Here you can inspect the solved grid and the full list of answers tied to each clue.",
|
||||
solvedGridTitle: "Solved grid",
|
||||
answersTitle: "Answer key",
|
||||
backToGame: "Back to game",
|
||||
answer: "Answer",
|
||||
clue: "Clue",
|
||||
},
|
||||
player: {
|
||||
topic: "Topic",
|
||||
difficulty: "Difficulty",
|
||||
lexicon: "Lexicon",
|
||||
},
|
||||
clues: {
|
||||
across: "Across",
|
||||
down: "Down",
|
||||
},
|
||||
},
|
||||
es: {
|
||||
home: {
|
||||
badge: "Alpha 01 Backoffice + Web UI",
|
||||
title: "Crucigramas a pedido, listos para jugar.",
|
||||
subtitle:
|
||||
"Esta primera interfaz en Next.js separa claramente el motor Python del frontend: el sitio envia una solicitud JSON, el backend genera el crucigrama y la pagina final lo convierte en una experiencia interactiva.",
|
||||
requestTitle: "Nueva solicitud",
|
||||
requestText:
|
||||
"Aqui el usuario configura el crucigrama. El formulario ya prepara la estructura de la solicitud y abre la pagina del juego real.",
|
||||
structureTitle: "Por que esta estructura",
|
||||
structureText:
|
||||
"El frontend no debe conocer los detalles del algoritmo: solo recibe el JSON final del crucigrama y lo transforma en una experiencia jugable, imprimible y reutilizable.",
|
||||
structureChips: ["Next.js UI", "Backend JSON", "Motor Python separado", "PDF y movil listos"],
|
||||
demoTitle: "Estado de la demo",
|
||||
openDemo: "Abrir demo jugable",
|
||||
openConfig: "Ir a configuracion",
|
||||
kpis: {
|
||||
words: "Palabras",
|
||||
intersections: "Intersecciones",
|
||||
rows: "Filas",
|
||||
cols: "Columnas",
|
||||
},
|
||||
},
|
||||
newPage: {
|
||||
badge: "Configuracion",
|
||||
title: "Pide al motor un nuevo crucigrama.",
|
||||
subtitle:
|
||||
"Esta pagina es el punto natural desde el que el sitio enviara al backend la solicitud JSON del contrato que hemos definido.",
|
||||
},
|
||||
form: {
|
||||
topic: "Tema",
|
||||
difficulty: "Dificultad",
|
||||
initialWords: "Palabras semilla",
|
||||
themedFillCount: "Palabras de relleno tematico",
|
||||
targetEmptyRatio: "Proporcion de vacios",
|
||||
seed: "Semilla",
|
||||
lexiconFile: "Lexico activo",
|
||||
submit: "Generar crucigrama",
|
||||
submitting: "Preparando...",
|
||||
note: "La solicitud ahora llama de verdad al motor Python y abre el crucigrama generado.",
|
||||
},
|
||||
play: {
|
||||
badge: "Crucigrama interactivo",
|
||||
subtitle: "Esta pagina carga el JSON real generado por el motor Python del backoffice.",
|
||||
loading: "Generacion en curso, cargando crucigrama...",
|
||||
errorPrefix: "Error de carga",
|
||||
overviewTitle: "Crucigrama listo para jugar",
|
||||
boardTitle: "Cuadricula de juego",
|
||||
cluesTitle: "Definiciones",
|
||||
instructions:
|
||||
"Haz clic en una casilla y escribe desde el teclado. Las definiciones siguen legibles en la columna lateral.",
|
||||
actions: {
|
||||
newCrossword: "Nuevo crucigrama",
|
||||
viewSolution: "Ver soluciones",
|
||||
},
|
||||
stats: {
|
||||
words: "Palabras",
|
||||
intersections: "Intersecciones",
|
||||
filledCells: "Casillas llenas",
|
||||
topicWords: "Palabras tematicas",
|
||||
},
|
||||
},
|
||||
solution: {
|
||||
badge: "Pagina de soluciones",
|
||||
title: "Revision final del crucigrama",
|
||||
subtitle:
|
||||
"Aqui puedes ver la cuadricula completa y la lista de respuestas asociadas a cada definicion.",
|
||||
solvedGridTitle: "Cuadricula resuelta",
|
||||
answersTitle: "Listado de soluciones",
|
||||
backToGame: "Volver al juego",
|
||||
answer: "Solucion",
|
||||
clue: "Definicion",
|
||||
},
|
||||
player: {
|
||||
topic: "Tema",
|
||||
difficulty: "Dificultad",
|
||||
lexicon: "Lexico",
|
||||
},
|
||||
clues: {
|
||||
across: "Horizontales",
|
||||
down: "Verticales",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function getDictionary(locale: Locale): Dictionary {
|
||||
return dictionaries[locale];
|
||||
}
|
||||
276
webapp/lib/mock-crossword.ts
Normal file
276
webapp/lib/mock-crossword.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
import type { Locale } from "@/lib/i18n";
|
||||
import type { CrosswordResponse } from "@/lib/types";
|
||||
|
||||
function buildCells(solutionRows: string[]) {
|
||||
const cells = [];
|
||||
const numbering = new Map<string, number>([
|
||||
["0:0", 1],
|
||||
["0:9", 2],
|
||||
["2:0", 3],
|
||||
["4:0", 4],
|
||||
["4:6", 5],
|
||||
["6:0", 6],
|
||||
]);
|
||||
|
||||
for (let row = 0; row < solutionRows.length; row += 1) {
|
||||
for (let col = 0; col < solutionRows[row].length; col += 1) {
|
||||
const char = solutionRows[row][col];
|
||||
if (char === "#") {
|
||||
cells.push({
|
||||
row,
|
||||
col,
|
||||
kind: "block" as const,
|
||||
solution: null,
|
||||
display: null,
|
||||
number: null,
|
||||
across_entry_id: null,
|
||||
down_entry_id: null,
|
||||
is_prefilled: false,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
cells.push({
|
||||
row,
|
||||
col,
|
||||
kind: "letter" as const,
|
||||
solution: char,
|
||||
display: "",
|
||||
number: numbering.get(`${row}:${col}`) ?? null,
|
||||
across_entry_id: null,
|
||||
down_entry_id: null,
|
||||
is_prefilled: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return cells;
|
||||
}
|
||||
|
||||
const solutionRows = [
|
||||
"AMBU#####PI",
|
||||
"L###T#####S",
|
||||
"TRENO#####T",
|
||||
"A###R#####A",
|
||||
"NAVE##MOTO#",
|
||||
"Z###R###O##",
|
||||
"AEROPORTO##",
|
||||
"######R#####",
|
||||
];
|
||||
|
||||
const titles: Record<Locale, string> = {
|
||||
it: "Cruciverba a tema trasporti",
|
||||
en: "Transport-themed crossword",
|
||||
es: "Crucigrama sobre transportes",
|
||||
};
|
||||
|
||||
const subtitles: Record<Locale, string> = {
|
||||
it: "Demo iniziale dell'interfaccia web",
|
||||
en: "Initial demo of the web interface",
|
||||
es: "Demo inicial de la interfaz web",
|
||||
};
|
||||
|
||||
const clueTexts: Record<Locale, string[]> = {
|
||||
it: [
|
||||
"Inizio di un mezzo di soccorso stradale e sanitario.",
|
||||
"Superficie destinata al decollo e all'atterraggio.",
|
||||
"Mezzo di trasporto che corre su rotaie.",
|
||||
"Grande mezzo per viaggiare sul mare.",
|
||||
"Veicolo a due ruote con motore.",
|
||||
"Complesso destinato al traffico degli aerei.",
|
||||
],
|
||||
en: [
|
||||
"Opening of a road and medical rescue vehicle.",
|
||||
"Surface used for takeoff and landing.",
|
||||
"Means of transport that runs on rails.",
|
||||
"Large vehicle used to travel by sea.",
|
||||
"Two-wheeled motor vehicle.",
|
||||
"Facility dedicated to airplane traffic.",
|
||||
],
|
||||
es: [
|
||||
"Inicio de un vehículo de socorro vial y sanitario.",
|
||||
"Superficie destinada al despegue y aterrizaje.",
|
||||
"Medio de transporte que circula sobre rieles.",
|
||||
"Gran vehículo para viajar por mar.",
|
||||
"Vehículo de dos ruedas con motor.",
|
||||
"Complejo destinado al tráfico de aviones.",
|
||||
],
|
||||
};
|
||||
|
||||
const baseEntries = [
|
||||
{
|
||||
entry_id: "A1",
|
||||
number: 1,
|
||||
direction: "across" as const,
|
||||
answer: "AMBU",
|
||||
answer_length: 4,
|
||||
row: 0,
|
||||
col: 0,
|
||||
cells: [[0, 0], [0, 1], [0, 2], [0, 3]] as [number, number][],
|
||||
clue_source: "demo",
|
||||
topics: ["transport", "health"],
|
||||
pos: "NOUN",
|
||||
is_seed: true,
|
||||
added_by_filler: false,
|
||||
confidence: 0.8,
|
||||
},
|
||||
{
|
||||
entry_id: "A2",
|
||||
number: 2,
|
||||
direction: "across" as const,
|
||||
answer: "PISTA",
|
||||
answer_length: 5,
|
||||
row: 0,
|
||||
col: 9,
|
||||
cells: [[0, 9], [0, 10], [0, 11], [1, 11], [2, 11]] as [number, number][],
|
||||
clue_source: "semantic_definition",
|
||||
topics: ["transport", "aviation"],
|
||||
pos: "NOUN",
|
||||
is_seed: false,
|
||||
added_by_filler: true,
|
||||
confidence: 0.94,
|
||||
},
|
||||
{
|
||||
entry_id: "A3",
|
||||
number: 3,
|
||||
direction: "across" as const,
|
||||
answer: "TRENO",
|
||||
answer_length: 5,
|
||||
row: 2,
|
||||
col: 0,
|
||||
cells: [[2, 0], [2, 1], [2, 2], [2, 3], [2, 4]] as [number, number][],
|
||||
clue_source: "semantic_definition",
|
||||
topics: ["transport"],
|
||||
pos: "NOUN",
|
||||
is_seed: true,
|
||||
added_by_filler: false,
|
||||
confidence: 0.98,
|
||||
},
|
||||
{
|
||||
entry_id: "A4",
|
||||
number: 4,
|
||||
direction: "across" as const,
|
||||
answer: "NAVE",
|
||||
answer_length: 4,
|
||||
row: 4,
|
||||
col: 0,
|
||||
cells: [[4, 0], [4, 1], [4, 2], [4, 3]] as [number, number][],
|
||||
clue_source: "semantic_definition",
|
||||
topics: ["transport", "sea"],
|
||||
pos: "NOUN",
|
||||
is_seed: true,
|
||||
added_by_filler: false,
|
||||
confidence: 0.96,
|
||||
},
|
||||
{
|
||||
entry_id: "A5",
|
||||
number: 5,
|
||||
direction: "across" as const,
|
||||
answer: "MOTO",
|
||||
answer_length: 4,
|
||||
row: 4,
|
||||
col: 6,
|
||||
cells: [[4, 6], [4, 7], [4, 8], [4, 9]] as [number, number][],
|
||||
clue_source: "semantic_definition",
|
||||
topics: ["transport"],
|
||||
pos: "NOUN",
|
||||
is_seed: true,
|
||||
added_by_filler: false,
|
||||
confidence: 0.95,
|
||||
},
|
||||
{
|
||||
entry_id: "A6",
|
||||
number: 6,
|
||||
direction: "across" as const,
|
||||
answer: "AEROPORTO",
|
||||
answer_length: 9,
|
||||
row: 6,
|
||||
col: 0,
|
||||
cells: [[6, 0], [6, 1], [6, 2], [6, 3], [6, 4], [6, 5], [6, 6], [6, 7], [6, 8]] as [number, number][],
|
||||
clue_source: "semantic_definition",
|
||||
topics: ["transport", "aviation"],
|
||||
pos: "NOUN",
|
||||
is_seed: true,
|
||||
added_by_filler: false,
|
||||
confidence: 0.97,
|
||||
},
|
||||
];
|
||||
|
||||
export function getMockCrosswordResponse(locale: Locale): CrosswordResponse {
|
||||
const texts = clueTexts[locale];
|
||||
const entries = baseEntries.map((entry, index) => ({
|
||||
...entry,
|
||||
clue: texts[index],
|
||||
}));
|
||||
|
||||
return {
|
||||
schema_version: "1.0",
|
||||
request_id: "req-demo-transport",
|
||||
crossword_id: "demo-transport",
|
||||
generated_at: "2026-04-29T11:30:00+02:00",
|
||||
status: "ok",
|
||||
generator: {
|
||||
topic: ["transport"],
|
||||
difficulty: "medium",
|
||||
seed: 2,
|
||||
runtime_lexicon: "lexicon_it_curated_llm_aggressive.json",
|
||||
},
|
||||
summary: {
|
||||
title: titles[locale],
|
||||
subtitle: subtitles[locale],
|
||||
rows: solutionRows.length,
|
||||
cols: solutionRows[0].length,
|
||||
total_words: 6,
|
||||
intersections: 8,
|
||||
},
|
||||
grid: {
|
||||
rows: solutionRows.length,
|
||||
cols: solutionRows[0].length,
|
||||
cell_size_hint: 42,
|
||||
cells: buildCells(solutionRows),
|
||||
},
|
||||
entries,
|
||||
clues: {
|
||||
across: entries.map((entry) => ({
|
||||
number: entry.number,
|
||||
entry_id: entry.entry_id,
|
||||
text: entry.clue,
|
||||
enumeration: entry.answer_length,
|
||||
topic_match: true,
|
||||
source: entry.clue_source,
|
||||
})),
|
||||
down: [],
|
||||
},
|
||||
solution: {
|
||||
grid_rows: solutionRows,
|
||||
words: ["AMBU", "PISTA", "TRENO", "NAVE", "MOTO", "AEROPORTO"],
|
||||
},
|
||||
diagnostics: {
|
||||
seed_words_requested: 19,
|
||||
seed_words_placed: 19,
|
||||
filler_words_added: 1,
|
||||
filled_cells: 35,
|
||||
empty_cells: 61,
|
||||
empty_ratio: 0.6354,
|
||||
target_empty_ratio: 0.1667,
|
||||
topic_words: 6,
|
||||
off_topic_words: 0,
|
||||
pos_counts: {
|
||||
sostantivi: 6,
|
||||
aggettivi: 0,
|
||||
verbi: 0,
|
||||
avverbi: 0,
|
||||
preposizioni: 0,
|
||||
congiunzioni: 0,
|
||||
altri: 0,
|
||||
},
|
||||
generation_seconds: 11.4,
|
||||
},
|
||||
artifacts: {
|
||||
pdf_player: null,
|
||||
pdf_solution: null,
|
||||
thumbnail: null,
|
||||
html_preview: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
115
webapp/lib/types.ts
Normal file
115
webapp/lib/types.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
export type CrosswordRequest = {
|
||||
schema_version: string;
|
||||
request_id: string;
|
||||
requested_at: string;
|
||||
generator: {
|
||||
topic: string[];
|
||||
difficulty: string;
|
||||
seed: number | null;
|
||||
initial_word_count: number;
|
||||
themed_fill_count: number;
|
||||
target_empty_ratio: number;
|
||||
diffxy: number;
|
||||
time_limit_seconds: number;
|
||||
max_candidates_per_word: number;
|
||||
lexicon_file: string;
|
||||
definitions_enabled: boolean;
|
||||
definition_style: string;
|
||||
preferred_output_language: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type CrosswordCell = {
|
||||
row: number;
|
||||
col: number;
|
||||
kind: "letter" | "block";
|
||||
solution: string | null;
|
||||
display: string | null;
|
||||
number: number | null;
|
||||
across_entry_id: string | null;
|
||||
down_entry_id: string | null;
|
||||
is_prefilled: boolean;
|
||||
};
|
||||
|
||||
export type CrosswordClue = {
|
||||
number: number;
|
||||
entry_id: string;
|
||||
text: string;
|
||||
enumeration: number;
|
||||
topic_match: boolean;
|
||||
source: string;
|
||||
};
|
||||
|
||||
export type CrosswordEntry = {
|
||||
entry_id: string;
|
||||
number: number;
|
||||
direction: "across" | "down";
|
||||
answer: string;
|
||||
answer_length: number;
|
||||
row: number;
|
||||
col: number;
|
||||
cells: [number, number][];
|
||||
clue: string;
|
||||
clue_source: string;
|
||||
topics: string[];
|
||||
pos: string;
|
||||
is_seed: boolean;
|
||||
added_by_filler: boolean;
|
||||
confidence: number;
|
||||
};
|
||||
|
||||
export type CrosswordResponse = {
|
||||
schema_version: string;
|
||||
request_id: string;
|
||||
crossword_id: string;
|
||||
generated_at: string;
|
||||
status: string;
|
||||
generator: {
|
||||
topic: string[];
|
||||
difficulty: string;
|
||||
seed: number | null;
|
||||
runtime_lexicon: string;
|
||||
};
|
||||
summary: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
rows: number;
|
||||
cols: number;
|
||||
total_words: number;
|
||||
intersections: number;
|
||||
};
|
||||
grid: {
|
||||
rows: number;
|
||||
cols: number;
|
||||
cell_size_hint: number;
|
||||
cells: CrosswordCell[];
|
||||
};
|
||||
entries: CrosswordEntry[];
|
||||
clues: {
|
||||
across: CrosswordClue[];
|
||||
down: CrosswordClue[];
|
||||
};
|
||||
solution: {
|
||||
grid_rows: string[];
|
||||
words: string[];
|
||||
};
|
||||
diagnostics: {
|
||||
seed_words_requested: number;
|
||||
seed_words_placed: number;
|
||||
filler_words_added: number;
|
||||
filled_cells: number;
|
||||
empty_cells: number;
|
||||
empty_ratio: number;
|
||||
target_empty_ratio: number;
|
||||
topic_words: number;
|
||||
off_topic_words: number;
|
||||
pos_counts: Record<string, number>;
|
||||
generation_seconds: number;
|
||||
};
|
||||
artifacts: {
|
||||
pdf_player: string | null;
|
||||
pdf_solution: string | null;
|
||||
thumbnail: string | null;
|
||||
html_preview: string | null;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user