feat(assistant): photo changes of a journal get their own card and ways back

The four new photo link tools join the journal's tools: the journey chat
offers them to editors, the trip chat loads them with the journey group and
the home chat keeps none. A journeyPhotos card shows the photos as
thumbnails with the entries they leave and reach, the first one marked as
the cover in a new order, and a caption old to new; a library add shows the
photos once they landed. A linked photo comes off again, an unlinked one
goes back, moved photos go back and a caption is written back, each while
nothing changed since. A new order of an entry's photos and a library add
have no way back yet. The journal prompt names the photo tools.
This commit is contained in:
Maurice
2026-10-24 22:07:46 +02:00
parent da43a81d26
commit 0050072b76
43 changed files with 1257 additions and 10 deletions
@@ -0,0 +1,57 @@
import { idSchema } from '../../common/primitives.schema';
import { cardCountSchema, cardDateSchema, cardText } from './card-parts';
import { z } from 'zod';
/**
* Photos of a journal as the assistant chat shows a change of them: one put
* on an entry or taken off it, photos moved from one entry to another, an
* entry's photos in a new order (the first is its cover), a caption changed,
* photos added from a library. A photo travels as its TREK photo id, which
* the client turns into TREK's own thumbnail address.
*/
/** How much a card carries; the server clips to the same numbers. */
export const JOURNEY_PHOTOS_LIMITS = { photos: 8, caption: 160, title: 100 } as const;
export const JOURNEY_PHOTOS_MODES = ['link', 'unlink', 'move', 'order', 'caption', 'add'] as const;
/** An entry the photos leave or reach. */
export const journeyPhotosEntrySchema = z.object({
entryId: idSchema.nullable(),
title: cardText(JOURNEY_PHOTOS_LIMITS.title).nullable(),
date: cardDateSchema.nullable(),
});
export type JourneyPhotosEntry = z.infer<typeof journeyPhotosEntrySchema>;
export const journeyPhotoLookSchema = z.object({
/** The TREK photo, for its thumbnail. */
photoId: idSchema,
video: z.boolean(),
caption: cardText(JOURNEY_PHOTOS_LIMITS.caption).nullable(),
});
export type JourneyPhotoLook = z.infer<typeof journeyPhotoLookSchema>;
export const journeyPhotosCardSchema = z.object({
kind: z.literal('journeyPhotos'),
journeyId: idSchema,
mode: z.enum(JOURNEY_PHOTOS_MODES),
/** The entry the photos leave (a move, an unlink). */
from: journeyPhotosEntrySchema.nullable(),
/** The entry the photos reach or whose photos change (a link, a move, a new order, an add). */
to: journeyPhotosEntrySchema.nullable(),
/** In the order the change leaves them; for a new order, the first is the entry's cover. */
photos: z.array(journeyPhotoLookSchema).max(JOURNEY_PHOTOS_LIMITS.photos),
/** Photos the change concerns that the card does not carry. */
more: cardCountSchema,
/** A move that leaves the photos on the first entry as well. */
kept: z.boolean(),
/** A caption change, old to new; null on every other change. */
caption: z
.object({
before: cardText(JOURNEY_PHOTOS_LIMITS.caption).nullable(),
after: cardText(JOURNEY_PHOTOS_LIMITS.caption).nullable(),
})
.nullable(),
});
export type JourneyPhotosCard = z.infer<typeof journeyPhotosCardSchema>;
+15 -2
View File
@@ -1,8 +1,10 @@
import { journeyEntriesCardSchema } from './journey-entries.card';
import { journeyHeaderCardSchema } from './journey-header.card';
import { journeyPhotosCardSchema } from './journey-photos.card';
export * from './journey-entries.card';
export * from './journey-header.card';
export * from './journey-photos.card';
/**
* What the travel journal brings to the assistant chat: the kinds of card it draws,
@@ -11,11 +13,16 @@ export * from './journey-header.card';
* spread these into the card union and the shared enums, so a new kind, verb
* or entity is added here and nowhere else.
*/
export const JOURNEY_CARD_SCHEMAS = [journeyEntriesCardSchema, journeyHeaderCardSchema] as const;
export const JOURNEY_CARD_SCHEMAS = [
journeyEntriesCardSchema,
journeyHeaderCardSchema,
journeyPhotosCardSchema,
] as const;
/**
* An entry written, changed or deleted, the entries put in a new order; a journal started, renamed, given
* another status or other settings, deleted; a trip linked or unlinked; its
* suggestions brought back; the user's own view of it changed.
* suggestions brought back; the user's own view of it changed; a photo put on
* an entry, taken off, moved, put in order, captioned or added.
*/
export const JOURNEY_VERB_KEYS = [
'addJournalEntry',
@@ -31,6 +38,12 @@ export const JOURNEY_VERB_KEYS = [
'unlinkJournalTrip',
'restoreJournalSuggestions',
'setJournalPreferences',
'linkJournalPhoto',
'unlinkJournalPhoto',
'moveJournalPhotos',
'orderJournalPhotos',
'captionJournalPhoto',
'addJournalPhotos',
] as const;
/** An entry of a journal, or the journal itself; the receipt opens it there. */
export const JOURNEY_APPLIED_ENTITIES = ['journey_entry', 'journey'] as const;