mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
d8213bfa42
Elements can be grouped on a page and picked, moved, resized, aligned and restacked as one. A layers panel lists the page front to back with lock, visibility, rename and drag to restack. The sheet gains marquee selection, arrow-key nudge, one box that resizes a multi-selection with Shift holding the proportions, and the design-tool shortcuts. The inspector lines a selection up and spaces it out, and copies and pastes a style. The assistant gets the same through the group, ungroup, name and hide operations, and describe_book_page lists the groups. The format gains the optional group, name and hidden fields and a spread's group names, so BOOK_SCHEMA_REV goes to 2.
506 lines
21 KiB
TypeScript
506 lines
21 KiB
TypeScript
import { idSchema } from '../common/primitives.schema';
|
|
import { BOOK_ELEMENT_OP_SCHEMAS, TEXT_STYLE_FIELDS, bindingNamesItsSource } from './book-element-ops.schema';
|
|
import { BOOK_GROUP_OP_SCHEMAS } from './book-group-ops.schema';
|
|
import { BOOK_COVER_TEMPLATES, BOOK_TEMPLATES } from './book-layouts';
|
|
import {
|
|
BOOK_BINDING_HINT,
|
|
bookFontIdSchema,
|
|
bookFrameStyleSchema,
|
|
bookPagePresetSchema,
|
|
bookPhotoFilterSchema,
|
|
bookPhotoFitSchema,
|
|
bookPhotoMaskSchema,
|
|
} from './book-op-enums';
|
|
import { bookElementIdSchema, bookGridIdSchema, bookSpreadIdSchema } from './book-op-ids';
|
|
import { bookPaintInSchema } from './book-paint.schema';
|
|
import {
|
|
MAX_BOOK_TITLE,
|
|
MAX_SPREADS,
|
|
MAX_SPREAD_ELEMENTS,
|
|
bookLayoutSchema,
|
|
bookPageNumbersSchema,
|
|
bookPageSetupSchema,
|
|
bookSpreadSchema,
|
|
bookTextElementSchema,
|
|
} from './book.schema';
|
|
import { PHOTO_PICK } from './photo-pick';
|
|
|
|
import { z } from 'zod';
|
|
|
|
/**
|
|
* Edits to a book, as the assistant's book tools take them and as a chat card
|
|
* carries them.
|
|
*
|
|
* One page per call, a bounded list of operations on it, every field bounded,
|
|
* nothing open: the tool arguments are what a model sends, and a shape that
|
|
* accepts any object accepts whatever it imagines. The fields come from the
|
|
* document's own schema (book.schema.ts) with its defaults taken off, so an
|
|
* operation that does not name a field leaves that field as it is rather than
|
|
* resetting it.
|
|
*
|
|
* Units are the document's: millimetres from the top-left of the spread, a
|
|
* double spread twice the page width with the fold at one page width.
|
|
*/
|
|
|
|
export { bookElementIdSchema, bookGridIdSchema, bookSpreadIdSchema };
|
|
|
|
export const BOOK_OP_LIMITS = {
|
|
/** Operations in one `edit_book_page` call. */
|
|
opsPerCall: 20,
|
|
/** Pages one call adds or deletes. */
|
|
pagesPerCall: 10,
|
|
/** Words one text operation sets. */
|
|
text: 2000,
|
|
/** Words one caption sets. */
|
|
caption: 300,
|
|
/** Elements one `remove` takes off a page. */
|
|
removePerOp: 30,
|
|
/** Findings one answer carries. */
|
|
findings: 40,
|
|
} as const;
|
|
|
|
const idsOf = (list: readonly { id: string }[]) => list.map((t) => t.id) as [string, ...string[]];
|
|
|
|
/** The spread layouts, by id. */
|
|
export const bookTemplateIdSchema = z
|
|
.enum(idsOf(BOOK_TEMPLATES))
|
|
.describe('A built-in double page layout by id; list_book_layouts says what each one holds.');
|
|
/** The single-page (cover and back) layouts, by id. */
|
|
export const bookCoverTemplateIdSchema = z
|
|
.enum(idsOf(BOOK_COVER_TEMPLATES))
|
|
.describe('A built-in cover or back layout by id; list_book_layouts says what each one holds.');
|
|
/** Either set: which one fits is the page's question, answered when the operation runs. */
|
|
export const bookAnyTemplateIdSchema = z
|
|
.enum([...idsOf(BOOK_TEMPLATES), ...idsOf(BOOK_COVER_TEMPLATES)])
|
|
.describe('A built-in layout by id: the cover-* ones fit a single page, the rest a double page.');
|
|
|
|
const text = bookTextElementSchema.shape;
|
|
|
|
/** A position in millimetres, inside the bounds the document takes. */
|
|
const mmIn = z.number().finite().min(-10000).max(10000);
|
|
/** A size the document keeps after rounding to hundredths: anything under that rounds to 0, which no frame may be. */
|
|
const sizeIn = z.number().finite().min(0.01).max(10000);
|
|
const unit01 = z.number().min(0).max(1);
|
|
|
|
const has = (value: object, keys: readonly string[]) =>
|
|
keys.some((k) => (value as Record<string, unknown>)[k] !== undefined);
|
|
|
|
const LOOK_KEYS = ['fit', 'focalX', 'focalY', 'mask', 'frameStyle', 'filter', 'radius'] as const;
|
|
const FRAME_KEYS = ['x', 'y', 'w', 'h', 'rotation'] as const;
|
|
const LAYOUT_KEYS = ['template', 'grid', 'saved', 'auto'] as const;
|
|
|
|
/** One edit of one page. Discriminated on `op`. */
|
|
export const bookPageOpSchema = z.discriminatedUnion('op', [
|
|
/**
|
|
* Lay the page out again: a built-in layout, a photo grid, one of the book's
|
|
* own layouts, or `auto`, the Studio's "this page again" from the entry the
|
|
* page was made from.
|
|
*/
|
|
z
|
|
.strictObject({
|
|
op: z.literal('layout'),
|
|
template: bookAnyTemplateIdSchema.optional(),
|
|
grid: bookGridIdSchema.optional(),
|
|
saved: z.string().min(1).max(40).optional(),
|
|
auto: z.literal(true).optional(),
|
|
})
|
|
.refine((v) => LAYOUT_KEYS.filter((k) => v[k] !== undefined).length === 1, {
|
|
message: 'give exactly one of template, grid, saved or auto',
|
|
}),
|
|
/** Put a gallery photo into a frame: by element id, by its place among the page's frames, or a new frame. */
|
|
z.strictObject({
|
|
op: z.literal('place_photo'),
|
|
photoId: idSchema,
|
|
target: z.union([
|
|
z.strictObject({ elementId: bookElementIdSchema }),
|
|
z.strictObject({
|
|
slot: z
|
|
.number()
|
|
.int()
|
|
.min(0)
|
|
.max(MAX_SPREAD_ELEMENTS - 1),
|
|
}),
|
|
z.literal('new'),
|
|
]),
|
|
fit: bookPhotoFitSchema.optional(),
|
|
focalX: unit01.optional(),
|
|
focalY: unit01.optional(),
|
|
}),
|
|
/** How a placed photo looks: the crop (fit and focal point) and the look around it. */
|
|
z
|
|
.strictObject({
|
|
op: z.literal('photo_look'),
|
|
elementId: bookElementIdSchema,
|
|
fit: bookPhotoFitSchema.optional(),
|
|
focalX: unit01.optional(),
|
|
focalY: unit01.optional(),
|
|
mask: bookPhotoMaskSchema.optional(),
|
|
frameStyle: bookFrameStyleSchema.optional(),
|
|
filter: bookPhotoFilterSchema.optional(),
|
|
radius: z.number().finite().min(0).max(500).optional(),
|
|
})
|
|
.refine((v) => has(v, LOOK_KEYS), { message: 'name at least one of the look fields' }),
|
|
/** Exchange the pictures of two frames; the frames stay where they are. */
|
|
z
|
|
.strictObject({ op: z.literal('swap_photos'), a: bookElementIdSchema, b: bookElementIdSchema })
|
|
.refine((v) => v.a !== v.b, { message: 'a and b must be two different frames' }),
|
|
/**
|
|
* Set or create a text. Addressed by element id, or by the role it plays on the
|
|
* page (the largest type is the heading, the longest of the rest the body, the
|
|
* next the meta line); a role the page has no text for creates one.
|
|
*/
|
|
z
|
|
.strictObject({
|
|
op: z.literal('text'),
|
|
elementId: bookElementIdSchema.optional(),
|
|
role: z
|
|
.enum(['heading', 'body', 'meta'])
|
|
.describe('heading is the largest type on the page, body the longest of the rest, meta the next.')
|
|
.optional(),
|
|
text: z.string().max(BOOK_OP_LIMITS.text).optional(),
|
|
binding: text.binding.unwrap().describe(`${BOOK_BINDING_HINT} Null unbinds.`).optional(),
|
|
...TEXT_STYLE_FIELDS,
|
|
})
|
|
.refine((v) => v.elementId !== undefined || v.role !== undefined, { message: 'name an elementId or a role' })
|
|
.refine((v) => bindingNamesItsSource(v.binding), {
|
|
message: 'an entry binding needs its entryId and a photo.caption binding its photoId',
|
|
path: ['binding'],
|
|
}),
|
|
/** A caption under a photo, bound to the photo's caption in the journey unless words are given. */
|
|
z.strictObject({
|
|
op: z.literal('caption'),
|
|
photoElementId: bookElementIdSchema,
|
|
text: z.string().max(BOOK_OP_LIMITS.caption).optional(),
|
|
}),
|
|
/** Move, resize or turn an element. */
|
|
z
|
|
.strictObject({
|
|
op: z.literal('frame'),
|
|
elementId: bookElementIdSchema,
|
|
x: mmIn.optional(),
|
|
y: mmIn.optional(),
|
|
w: sizeIn.optional(),
|
|
h: sizeIn.optional(),
|
|
rotation: z.number().finite().min(-360).max(360).optional(),
|
|
})
|
|
.refine((v) => has(v, FRAME_KEYS), { message: 'name at least one of x, y, w, h or rotation' }),
|
|
/** Take elements off the page: parked by default (kept with the page, back on the next layout), or deleted. */
|
|
z.strictObject({
|
|
op: z.literal('remove'),
|
|
elementIds: z.array(bookElementIdSchema).min(1).max(BOOK_OP_LIMITS.removePerOp),
|
|
park: z.boolean().default(true),
|
|
}),
|
|
/** The page colour or paint; null is the paper. Exactly one of the two. */
|
|
z
|
|
.strictObject({
|
|
op: z.literal('background'),
|
|
color: bookSpreadSchema.shape.background.unwrap().optional(),
|
|
paint: bookPaintInSchema.nullable().optional(),
|
|
})
|
|
.refine((v) => (v.color === undefined) !== (v.paint === undefined), { message: 'give the color or the paint' }),
|
|
...BOOK_ELEMENT_OP_SCHEMAS,
|
|
...BOOK_GROUP_OP_SCHEMAS,
|
|
]);
|
|
export type BookPageOp = z.infer<typeof bookPageOpSchema>;
|
|
export type BookPageOpInput = z.input<typeof bookPageOpSchema>;
|
|
|
|
/*
|
|
* ── Tool arguments ──────────────────────────────────────────────────────
|
|
*
|
|
* Plain shapes, so a `*.mcp.ts` spreads them after its own `journeyId`
|
|
* (`inputSchema: { journeyId: idSchema, ...editBookPageArgs }`) and holds no
|
|
* `z.` of its own. Each also exists as a strict `*RequestSchema`, which is what
|
|
* `contracts:open` measures: none of them may hold an open shape.
|
|
*/
|
|
|
|
const returnPrevious = z.boolean().optional();
|
|
/** Refuses the write (409) unless the book is still at this version; the assistant's undo sets it. */
|
|
const expectedVersion = z.number().int().nonnegative().optional();
|
|
|
|
export const editBookPageArgs = {
|
|
spreadId: bookSpreadIdSchema,
|
|
ops: z.array(bookPageOpSchema).min(1).max(BOOK_OP_LIMITS.opsPerCall),
|
|
returnPrevious,
|
|
};
|
|
export const addBookPagesArgs = {
|
|
/** The new pages go after this one, kept inside the covers. */
|
|
afterSpreadId: bookSpreadIdSchema,
|
|
count: z.number().int().min(1).max(BOOK_OP_LIMITS.pagesPerCall).optional(),
|
|
template: bookTemplateIdSchema.optional(),
|
|
entryId: idSchema.optional(),
|
|
};
|
|
export const moveBookPageArgs = {
|
|
spreadId: bookSpreadIdSchema,
|
|
toIndex: z
|
|
.number()
|
|
.int()
|
|
.min(0)
|
|
.max(MAX_SPREADS - 1),
|
|
expectedVersion,
|
|
};
|
|
export const deleteBookPagesArgs = {
|
|
spreadIds: z.array(bookSpreadIdSchema).min(1).max(BOOK_OP_LIMITS.pagesPerCall),
|
|
returnPrevious,
|
|
expectedVersion,
|
|
};
|
|
export const setBookCoverArgs = {
|
|
template: bookCoverTemplateIdSchema.optional(),
|
|
photoId: idSchema.optional(),
|
|
title: z.string().max(MAX_BOOK_TITLE).optional(),
|
|
subtitle: z.string().max(MAX_BOOK_TITLE).optional(),
|
|
returnPrevious,
|
|
};
|
|
export const createBookArgs = {
|
|
title: z.string().max(MAX_BOOK_TITLE).optional(),
|
|
preset: bookPagePresetSchema.optional(),
|
|
};
|
|
/**
|
|
* A page as it was before a write, for the assistant's undo: `index` is where
|
|
* it goes back when it is gone, a page still in the book is replaced where it is.
|
|
*/
|
|
export const bookPreviousSpreadSchema = z.object({
|
|
index: z
|
|
.number()
|
|
.int()
|
|
.min(0)
|
|
.max(MAX_SPREADS - 1),
|
|
spread: bookSpreadSchema,
|
|
});
|
|
export type BookPreviousSpread = z.infer<typeof bookPreviousSpreadSchema>;
|
|
|
|
/**
|
|
* A page as a write left it, for the assistant's undo: its id and a digest of
|
|
* the page as the book stores it, which the write answered under
|
|
* `page_digests` (with `returnPrevious`). A null digest names a page the
|
|
* write took out of the book.
|
|
*/
|
|
export const bookPageDigestSchema = z.strictObject({
|
|
spreadId: bookSpreadIdSchema,
|
|
digest: z
|
|
.string()
|
|
.regex(/^[0-9a-f]{16}$/)
|
|
.nullable(),
|
|
});
|
|
export type BookPageDigest = z.infer<typeof bookPageDigestSchema>;
|
|
|
|
/**
|
|
* The pages a write answered under `previous_spreads`, put back in one call
|
|
* (several deleted pages are one undo), lowest index first. The book must
|
|
* still be at `expectedVersion`. Every page `expectedPages` names must still
|
|
* be as its digest says, and only when they name every page put back with a
|
|
* digest do they replace the version, so a later save to another page does
|
|
* not hold the undo back; a page put back that they leave out or name as
|
|
* gone keeps the version's check.
|
|
*/
|
|
export const restoreBookPageArgs = {
|
|
pages: z.array(bookPreviousSpreadSchema).min(1).max(BOOK_OP_LIMITS.pagesPerCall),
|
|
expectedVersion: z.number().int().nonnegative(),
|
|
expectedPages: z.array(bookPageDigestSchema).min(1).max(BOOK_OP_LIMITS.pagesPerCall).optional(),
|
|
};
|
|
/** A design draft's id, as `design_book` answers it. */
|
|
export const bookDesignIdSchema = z.string().regex(/^[\w-]{1,40}$/);
|
|
|
|
export const describeBookPageArgs = { spreadId: bookSpreadIdSchema, designId: bookDesignIdSchema.optional() };
|
|
export const listBookLayoutsArgs = {
|
|
role: z.enum(['inner', 'single']).describe('inner for double pages, single for the cover and back.').optional(),
|
|
};
|
|
export const suggestBookPhotosArgs = {
|
|
entryId: idSchema.optional(),
|
|
date: z
|
|
.string()
|
|
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
.optional(),
|
|
purpose: z
|
|
.enum(['page', 'cover'])
|
|
.describe(
|
|
'cover prefers wide or square pictures with enough pixels for a whole cover (2400 px on the long edge); page is for an inner page.',
|
|
)
|
|
.optional(),
|
|
limit: z.number().int().min(1).max(PHOTO_PICK.maxLimit).optional(),
|
|
};
|
|
|
|
const pageNumbers = bookPageNumbersSchema.shape;
|
|
|
|
/**
|
|
* The page format and the page numbers, as the Studio's setup sets them.
|
|
* Elements are not rescaled when the size changes, as in the Studio; the
|
|
* answer carries `previous_setup` to set it back.
|
|
*/
|
|
export const setBookSetupArgs = {
|
|
preset: bookPagePresetSchema.optional(),
|
|
pageWidth: z.number().finite().min(60).max(500).optional(),
|
|
pageHeight: z.number().finite().min(60).max(500).optional(),
|
|
bleed: z.number().finite().min(0).max(20).optional(),
|
|
safe: z.number().finite().min(0).max(50).optional(),
|
|
pageNumbers: z
|
|
.strictObject({
|
|
show: pageNumbers.show.unwrap().optional(),
|
|
startAt: pageNumbers.startAt.unwrap().optional(),
|
|
position: pageNumbers.position
|
|
.unwrap()
|
|
.describe('outer edge, inner edge by the fold, or centre of the foot.')
|
|
.optional(),
|
|
margin: z.number().finite().min(0).max(60).optional(),
|
|
size: pageNumbers.size.unwrap().optional(),
|
|
autoColor: pageNumbers.autoColor.unwrap().optional(),
|
|
color: pageNumbers.color.unwrap().optional(),
|
|
font: bookFontIdSchema.optional(),
|
|
})
|
|
.optional(),
|
|
/** The assistant's undo sets the setup back only while the book is still at the version the change left. */
|
|
expectedVersion,
|
|
};
|
|
/** A copy of an inner page, with new ids, right after it. */
|
|
export const duplicateBookPageArgs = { spreadId: bookSpreadIdSchema };
|
|
/** Keep a page's arrangement as one of the book's own layouts. */
|
|
export const saveBookLayoutArgs = { spreadId: bookSpreadIdSchema, name: z.string().trim().min(1).max(60).optional() };
|
|
/** Take one of the book's own layouts away; the answer carries it in `previous_layout`. */
|
|
export const removeBookLayoutArgs = { layoutId: z.string().min(1).max(40), expectedVersion };
|
|
|
|
export const editBookPageRequestSchema = z.strictObject(editBookPageArgs);
|
|
export const addBookPagesRequestSchema = z.strictObject(addBookPagesArgs);
|
|
export const moveBookPageRequestSchema = z.strictObject(moveBookPageArgs);
|
|
export const deleteBookPagesRequestSchema = z.strictObject(deleteBookPagesArgs);
|
|
export const setBookCoverRequestSchema = z.strictObject(setBookCoverArgs);
|
|
export const createBookRequestSchema = z.strictObject(createBookArgs);
|
|
export const restoreBookPageRequestSchema = z.strictObject(restoreBookPageArgs);
|
|
export const describeBookPageRequestSchema = z.strictObject(describeBookPageArgs);
|
|
export const listBookLayoutsRequestSchema = z.strictObject(listBookLayoutsArgs);
|
|
export const suggestBookPhotosRequestSchema = z.strictObject(suggestBookPhotosArgs);
|
|
export const setBookSetupRequestSchema = z.strictObject(setBookSetupArgs);
|
|
export const duplicateBookPageRequestSchema = z.strictObject(duplicateBookPageArgs);
|
|
export const saveBookLayoutRequestSchema = z.strictObject(saveBookLayoutArgs);
|
|
export const removeBookLayoutRequestSchema = z.strictObject(removeBookLayoutArgs);
|
|
|
|
export type EditBookPageRequest = z.infer<typeof editBookPageRequestSchema>;
|
|
export type AddBookPagesRequest = z.infer<typeof addBookPagesRequestSchema>;
|
|
export type MoveBookPageRequest = z.infer<typeof moveBookPageRequestSchema>;
|
|
export type DeleteBookPagesRequest = z.infer<typeof deleteBookPagesRequestSchema>;
|
|
export type SetBookCoverRequest = z.infer<typeof setBookCoverRequestSchema>;
|
|
export type CreateBookRequest = z.infer<typeof createBookRequestSchema>;
|
|
export type RestoreBookPageRequest = z.infer<typeof restoreBookPageRequestSchema>;
|
|
export type SuggestBookPhotosRequest = z.infer<typeof suggestBookPhotosRequestSchema>;
|
|
export type SetBookSetupRequest = z.infer<typeof setBookSetupRequestSchema>;
|
|
export type DuplicateBookPageRequest = z.infer<typeof duplicateBookPageRequestSchema>;
|
|
export type SaveBookLayoutRequest = z.infer<typeof saveBookLayoutRequestSchema>;
|
|
export type RemoveBookLayoutRequest = z.infer<typeof removeBookLayoutRequestSchema>;
|
|
|
|
/*
|
|
* ── Answers ─────────────────────────────────────────────────────────────
|
|
*
|
|
* What the tools answer with, for the server's tests and the assistant's card
|
|
* builders. A summary of a page rather than the page: a book reaches megabytes
|
|
* and a tool answer is cut at 40 000 characters.
|
|
*/
|
|
|
|
export const BOOK_FINDING_CODES = [
|
|
'overlap',
|
|
'fold',
|
|
'trim',
|
|
'empty_frame',
|
|
'low_dpi',
|
|
'focal_cut',
|
|
'text_overflow',
|
|
'parked',
|
|
'hidden_photo',
|
|
'not_in_gallery',
|
|
'text_on_busy',
|
|
'contrast_low',
|
|
'small_text',
|
|
'crowded',
|
|
'off_grid',
|
|
'font_mix',
|
|
'palette_drift',
|
|
'repeat_layout',
|
|
] as const;
|
|
export type BookFindingCode = (typeof BOOK_FINDING_CODES)[number];
|
|
|
|
/** `severe` is what the assistant fixes before it proposes; `warn` it should mention; `info` is context. */
|
|
export const bookFindingSchema = z.object({
|
|
code: z.enum(BOOK_FINDING_CODES),
|
|
severity: z.enum(['severe', 'warn', 'info']),
|
|
elementIds: z.array(z.string()).max(8),
|
|
message: z.string().max(300),
|
|
});
|
|
export type BookFinding = z.infer<typeof bookFindingSchema>;
|
|
|
|
/** One page as `get_book` lists it and a write answers with it. */
|
|
export const bookSpreadSummarySchema = z.object({
|
|
index: z.number().int().nonnegative(),
|
|
id: z.string(),
|
|
role: bookSpreadSchema.shape.role.unwrap(),
|
|
entryId: z.number().int().nullable(),
|
|
background: z.string().nullable(),
|
|
photoIds: z.array(z.number().int()),
|
|
/** The first 60 characters of each text, in paint order. */
|
|
texts: z.array(z.string().max(60)),
|
|
elements: z.number().int().nonnegative(),
|
|
emptyFrames: z.number().int().nonnegative(),
|
|
parked: z.number().int().nonnegative(),
|
|
});
|
|
export type BookSpreadSummary = z.infer<typeof bookSpreadSummarySchema>;
|
|
|
|
/** `get_book`: the book without its document. */
|
|
export const bookOverviewSchema = z.object({
|
|
exists: z.boolean(),
|
|
version: z.number().int().nonnegative().nullable(),
|
|
title: z.string(),
|
|
page: bookPageSetupSchema.pick({ preset: true, pageWidth: true, pageHeight: true, bleed: true, safe: true }),
|
|
spreads: z.array(bookSpreadSummarySchema),
|
|
layouts: z.array(z.object({ id: z.string(), name: z.string(), role: z.enum(['inner', 'single']) })),
|
|
});
|
|
export type BookOverview = z.infer<typeof bookOverviewSchema>;
|
|
|
|
/** One element as `describe_book_page` lists it, back to front. */
|
|
export const bookElementSummarySchema = z.object({
|
|
id: z.string(),
|
|
kind: z.string(),
|
|
frame: z.object({ x: z.number(), y: z.number(), w: z.number(), h: z.number() }),
|
|
rotation: z.number(),
|
|
locked: z.boolean(),
|
|
photoId: z.number().int().nullable().optional(),
|
|
text: z.string().max(80).optional(),
|
|
binding: z.string().optional(),
|
|
/** The group it is in, its layer name, and whether it is hidden; absent when not. */
|
|
group: z.string().optional(),
|
|
name: z.string().optional(),
|
|
hidden: z.boolean().optional(),
|
|
});
|
|
export type BookElementSummary = z.infer<typeof bookElementSummarySchema>;
|
|
|
|
/** `describe_book_page`: the words a model without eyes checks a page by. */
|
|
export const bookPageDescriptionSchema = z.object({
|
|
spreadId: z.string(),
|
|
index: z.number().int().nonnegative(),
|
|
role: bookSpreadSchema.shape.role.unwrap(),
|
|
text: z.string(),
|
|
findings: z.array(bookFindingSchema).max(BOOK_OP_LIMITS.findings),
|
|
elements: z.array(bookElementSummarySchema),
|
|
});
|
|
export type BookPageDescription = z.infer<typeof bookPageDescriptionSchema>;
|
|
|
|
/**
|
|
* What a book write answers. `dropped` names the elements whose content left
|
|
* the page (a layout, a hard remove). `previous_spreads` and `page_digests`
|
|
* only when the call asked with `returnPrevious`; `from_index` only from a
|
|
* move, where the page stood before it, so a move can be taken back.
|
|
*/
|
|
export const bookWriteResultSchema = z.object({
|
|
version: z.number().int().nonnegative(),
|
|
spread_ids: z.array(z.string()),
|
|
pages: z.array(bookSpreadSummarySchema),
|
|
findings: z.array(bookFindingSchema).max(BOOK_OP_LIMITS.findings),
|
|
dropped: z.array(z.string()).max(MAX_SPREAD_ELEMENTS * 2),
|
|
previous_spreads: z.array(bookPreviousSpreadSchema).optional(),
|
|
/** The pages the write touched as it left them, for an undo that checks only those (`expectedPages`). */
|
|
page_digests: z.array(bookPageDigestSchema).max(MAX_SPREADS).optional(),
|
|
from_index: z.number().int().nonnegative().optional(),
|
|
/** From `set_book_setup`: the setup as it was, to set it back. */
|
|
previous_setup: bookPageSetupSchema.optional(),
|
|
/** From `remove_book_layout`: the layout as it was, to put it back. */
|
|
previous_layout: bookLayoutSchema.optional(),
|
|
/** From `save_book_layout`: the new layout's id, to take it away again. */
|
|
layout_id: z.string().max(40).optional(),
|
|
});
|
|
export type BookWriteResult = z.infer<typeof bookWriteResultSchema>;
|