mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 15:36:33 +00:00
feat(assistant): a card framework every addon package plugs into
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
data: {"type":"start","messageId":"msg-12"}
|
||||
|
||||
data: {"type":"tool-output-available","toolCallId":"7c1e2d3a-0b4f-4e8a-9d6c-1f2e3a4b5c6d","output":{"tour":{"place_id":77,"name":"Ridge walk"}}}
|
||||
|
||||
data: {"type":"data-trek-applied","data":{"toolCallId":"7c1e2d3a-0b4f-4e8a-9d6c-1f2e3a4b5c6d","entity":"tour","ids":[77],"dayId":32,"undoUntil":"2026-06-11T09:15:00Z"}}
|
||||
|
||||
data: {"type":"data-trek-card","data":{"toolCallId":"7c1e2d3a-0b4f-4e8a-9d6c-1f2e3a4b5c6d","card":{"kind":"tourRoute"}}}
|
||||
|
||||
data: {"type":"start-step"}
|
||||
|
||||
data: {"type":"tool-input-start","toolCallId":"toolu_02","toolName":"list_tours"}
|
||||
|
||||
data: {"type":"tool-input-available","toolCallId":"toolu_02","toolName":"list_tours","input":{}}
|
||||
|
||||
data: {"type":"tool-output-available","toolCallId":"toolu_02","output":{"tours":[{"place_id":77,"name":"Ridge walk"}]}}
|
||||
|
||||
data: {"type":"data-trek-card","data":{"toolCallId":"toolu_02","card":{"kind":"tourList"}}}
|
||||
|
||||
data: {"type":"finish-step"}
|
||||
|
||||
data: {"type":"start-step"}
|
||||
|
||||
data: {"type":"text-start","id":"txt-0"}
|
||||
|
||||
data: {"type":"text-delta","id":"txt-0","delta":"The ridge walk is on Friday now. "}
|
||||
|
||||
data: {"type":"text-delta","id":"txt-0","delta":"A lake loop would fit on Saturday."}
|
||||
|
||||
data: {"type":"text-end","id":"txt-0"}
|
||||
|
||||
data: {"type":"tool-input-start","toolCallId":"9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","toolName":"create_hike"}
|
||||
|
||||
data: {"type":"tool-input-available","toolCallId":"9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","toolName":"create_hike","input":{"name":"Lake loop","round_trip":true}}
|
||||
|
||||
data: {"type":"tool-approval-request","approvalId":"9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","toolCallId":"9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"}
|
||||
|
||||
data: {"type":"data-trek-change","data":{"toolCallId":"9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","verbKey":"addTour","subject":"Lake loop","rows":[{"op":"add","labelKey":"route","before":null,"after":"Car park → Lake → Car park"}],"dayId":null,"placeIds":[],"undoable":true,"card":{"kind":"tourRoute"}}}
|
||||
|
||||
data: {"type":"data-trek-quota","data":{"used":4,"limit":50}}
|
||||
|
||||
data: {"type":"finish","finishReason":"tool-calls"}
|
||||
|
||||
data: [DONE]
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
assistantAddFoundPlaceResponseSchema,
|
||||
assistantAttachmentRequestSchema,
|
||||
assistantChangePreviewSchema,
|
||||
assistantChangePreviewWriteSchema,
|
||||
assistantChatRequestSchema,
|
||||
assistantDecisionsRequestSchema,
|
||||
assistantFoundPlaceSchema,
|
||||
@@ -122,7 +123,13 @@ describe('assistantChangePreviewSchema', () => {
|
||||
expect(
|
||||
assistantChangePreviewSchema.safeParse({ ...preview, rows: [{ ...preview.rows[0], labelKey: 'evil' }] }).success,
|
||||
).toBe(false);
|
||||
expect(assistantChangePreviewSchema.safeParse({ ...preview, verbKey: 'deleteTrip' }).success).toBe(false);
|
||||
expect(assistantChangePreviewWriteSchema.safeParse({ ...preview, verbKey: 'deleteTrip' }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('reads a verb a later build added as a plain update, and writes none it does not know', () => {
|
||||
expect(assistantChangePreviewSchema.parse({ ...preview, verbKey: 'laterVerb' }).verbKey).toBe('genericUpdate');
|
||||
expect(assistantChangePreviewWriteSchema.parse(preview).verbKey).toBe('addPlace');
|
||||
expect(assistantChangePreviewWriteSchema.safeParse({ ...preview, card: { kind: 'nope' } }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('holds at most twelve rows', () => {
|
||||
|
||||
@@ -1,4 +1,31 @@
|
||||
import { idParamSchema, idSchema } from '../common/primitives.schema';
|
||||
import {
|
||||
assistantCardSchema,
|
||||
assistantCardWriteSchema,
|
||||
ATLAS_APPLIED_ENTITIES,
|
||||
ATLAS_VERB_KEYS,
|
||||
BOOKING_APPLIED_ENTITIES,
|
||||
BOOKING_VERB_KEYS,
|
||||
BUDGET_APPLIED_ENTITIES,
|
||||
BUDGET_VERB_KEYS,
|
||||
COLLAB_APPLIED_ENTITIES,
|
||||
COLLAB_VERB_KEYS,
|
||||
COLLECTION_APPLIED_ENTITIES,
|
||||
COLLECTION_VERB_KEYS,
|
||||
JOURNEY_APPLIED_ENTITIES,
|
||||
JOURNEY_VERB_KEYS,
|
||||
PACKING_APPLIED_ENTITIES,
|
||||
PACKING_VERB_KEYS,
|
||||
ROADTRIP_APPLIED_ENTITIES,
|
||||
ROADTRIP_VERB_KEYS,
|
||||
storedTrekCardSchema,
|
||||
TOUR_APPLIED_ENTITIES,
|
||||
TOUR_VERB_KEYS,
|
||||
trekCardSchema,
|
||||
VACAY_APPLIED_ENTITIES,
|
||||
VACAY_VERB_KEYS,
|
||||
type AssistantCard,
|
||||
} from './cards';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -48,9 +75,10 @@ export type AssistantErrorCode = z.infer<typeof assistantErrorCodeSchema>;
|
||||
|
||||
/**
|
||||
* What a change does, one per writing tool. The client labels the card with
|
||||
* `assistant.change.verb.<key>`.
|
||||
* `assistant.change.verb.<key>`. These are the core verbs; each part of TREK
|
||||
* adds its own in its cards contract (`cards/<pkg>.contract.ts`).
|
||||
*/
|
||||
export const ASSISTANT_CHANGE_VERB_KEYS = [
|
||||
export const CORE_VERB_KEYS = [
|
||||
'addPlace',
|
||||
'addPlaceToDay',
|
||||
'updatePlace',
|
||||
@@ -76,6 +104,19 @@ export const ASSISTANT_CHANGE_VERB_KEYS = [
|
||||
'genericUpdate',
|
||||
'genericRemove',
|
||||
] as const;
|
||||
export const ASSISTANT_CHANGE_VERB_KEYS = [
|
||||
...CORE_VERB_KEYS,
|
||||
...TOUR_VERB_KEYS,
|
||||
...ROADTRIP_VERB_KEYS,
|
||||
...BUDGET_VERB_KEYS,
|
||||
...BOOKING_VERB_KEYS,
|
||||
...COLLAB_VERB_KEYS,
|
||||
...COLLECTION_VERB_KEYS,
|
||||
...ATLAS_VERB_KEYS,
|
||||
...VACAY_VERB_KEYS,
|
||||
...JOURNEY_VERB_KEYS,
|
||||
...PACKING_VERB_KEYS,
|
||||
] as const;
|
||||
export const assistantChangeVerbKeySchema = z.enum(ASSISTANT_CHANGE_VERB_KEYS);
|
||||
export type AssistantChangeVerbKey = z.infer<typeof assistantChangeVerbKeySchema>;
|
||||
|
||||
@@ -233,6 +274,19 @@ export const assistantAddFoundPlaceRequestSchema = assistantClientCtxSchema
|
||||
.strict();
|
||||
export type AssistantAddFoundPlaceRequest = z.infer<typeof assistantAddFoundPlaceRequestSchema>;
|
||||
|
||||
/**
|
||||
* POST /threads/:threadId/proposals: take a proposal an answer showed as a
|
||||
* card ("Plan this"). `toolCallId` names the read whose card it was; the
|
||||
* server holds the matching change like a found place, from what it stored,
|
||||
* and answers like the found places route.
|
||||
*/
|
||||
export const assistantTakeProposalRequestSchema = assistantClientCtxSchema
|
||||
.extend({
|
||||
toolCallId: z.string().min(1).max(64),
|
||||
})
|
||||
.strict();
|
||||
export type AssistantTakeProposalRequest = z.infer<typeof assistantTakeProposalRequestSchema>;
|
||||
|
||||
// ── Change previews ─────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -269,8 +323,8 @@ export const assistantChangeRowSchema = z.object({
|
||||
});
|
||||
export type AssistantChangeRow = z.infer<typeof assistantChangeRowSchema>;
|
||||
|
||||
/** A held writing call as the user sees it before deciding. */
|
||||
export const assistantChangePreviewSchema = z.object({
|
||||
/** A held writing call as the server writes it: every verb and card one this build knows. */
|
||||
export const assistantChangePreviewWriteSchema = z.object({
|
||||
toolCallId: z.string(),
|
||||
verbKey: assistantChangeVerbKeySchema,
|
||||
// What the change is about: the place, note or item name.
|
||||
@@ -286,28 +340,63 @@ export const assistantChangePreviewSchema = z.object({
|
||||
// The tool behind a card without one of its own, and the part of TREK it works in.
|
||||
tool: z.string().max(80).optional(),
|
||||
domain: z.string().max(40).optional(),
|
||||
// What the change will do, drawn as a card; absent on older rows and on cards without one.
|
||||
card: trekCardSchema.nullable().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* A held writing call as the user sees it before deciding, and as it is read
|
||||
* back: a verb a later build added reads as a plain update and a card this
|
||||
* build cannot draw as none, so an older client or a rolled back server still
|
||||
* shows the change instead of dropping it.
|
||||
*/
|
||||
export const assistantChangePreviewSchema = assistantChangePreviewWriteSchema.extend({
|
||||
verbKey: assistantChangeVerbKeySchema.catch('genericUpdate'),
|
||||
card: storedTrekCardSchema.optional(),
|
||||
});
|
||||
export type AssistantChangePreview = z.infer<typeof assistantChangePreviewSchema>;
|
||||
|
||||
/** An applied change: what it touched, and until when it can be undone. */
|
||||
export const assistantAppliedSchema = z.object({
|
||||
/** What an applied change can touch in the core; each part of TREK adds its own in its cards contract. */
|
||||
export const CORE_APPLIED_ENTITIES = [
|
||||
'place',
|
||||
'assignment',
|
||||
'day_note',
|
||||
'packing',
|
||||
'todo',
|
||||
'budget',
|
||||
'reservation',
|
||||
'memory',
|
||||
'tour',
|
||||
'other',
|
||||
] as const;
|
||||
|
||||
const assistantAppliedEntitySchema = z.enum([
|
||||
...CORE_APPLIED_ENTITIES,
|
||||
...TOUR_APPLIED_ENTITIES,
|
||||
...ROADTRIP_APPLIED_ENTITIES,
|
||||
...BUDGET_APPLIED_ENTITIES,
|
||||
...BOOKING_APPLIED_ENTITIES,
|
||||
...COLLAB_APPLIED_ENTITIES,
|
||||
...COLLECTION_APPLIED_ENTITIES,
|
||||
...ATLAS_APPLIED_ENTITIES,
|
||||
...VACAY_APPLIED_ENTITIES,
|
||||
...JOURNEY_APPLIED_ENTITIES,
|
||||
...PACKING_APPLIED_ENTITIES,
|
||||
]);
|
||||
|
||||
/** An applied change as the server writes it: what it touched, and until when it can be undone. */
|
||||
const assistantAppliedWriteSchema = z.object({
|
||||
toolCallId: z.string(),
|
||||
entity: z.enum([
|
||||
'place',
|
||||
'assignment',
|
||||
'day_note',
|
||||
'packing',
|
||||
'todo',
|
||||
'budget',
|
||||
'reservation',
|
||||
'memory',
|
||||
'tour',
|
||||
'other',
|
||||
]),
|
||||
entity: assistantAppliedEntitySchema,
|
||||
ids: z.array(idSchema),
|
||||
dayId: idSchema.nullable(),
|
||||
undoUntil: z.string().nullable(),
|
||||
});
|
||||
|
||||
/** An applied change as it is read back: an entity a later build added reads as `other`, with nothing to open. */
|
||||
export const assistantAppliedSchema = assistantAppliedWriteSchema.extend({
|
||||
entity: assistantAppliedEntitySchema.catch('other'),
|
||||
});
|
||||
export type AssistantApplied = z.infer<typeof assistantAppliedSchema>;
|
||||
|
||||
// ── Cards in an answer ──────────────────────────────────────────────────────
|
||||
@@ -453,34 +542,73 @@ export const assistantToolApprovalSchema = z.object({
|
||||
});
|
||||
export type AssistantToolApproval = z.infer<typeof assistantToolApprovalSchema>;
|
||||
|
||||
/** One stored part of a message, in the order the model produced it. */
|
||||
export const assistantMessagePartSchema = z.discriminatedUnion('type', [
|
||||
z.object({ type: z.literal('text'), text: z.string() }),
|
||||
// A file the user handed the assistant. Only its description is kept, never its content.
|
||||
z.object({
|
||||
type: z.literal('attachment'),
|
||||
name: z.string(),
|
||||
mime: z.string(),
|
||||
size: z.number().int().nonnegative(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('tool-call'),
|
||||
toolCallId: z.string(),
|
||||
toolName: z.string(),
|
||||
args: z.record(z.string(), z.unknown()),
|
||||
result: z.unknown().optional(),
|
||||
isError: z.boolean().optional(),
|
||||
approval: assistantToolApprovalSchema.optional(),
|
||||
}),
|
||||
z.object({ type: z.literal('data-trek-change'), data: assistantChangePreviewSchema }),
|
||||
z.object({ type: z.literal('data-trek-applied'), data: assistantAppliedSchema }),
|
||||
z.object({ type: z.literal('data-trek-places'), data: assistantPlacesSchema }),
|
||||
z.object({ type: z.literal('data-trek-weather'), data: assistantWeatherSchema }),
|
||||
// Only on a user message.
|
||||
z.object({ type: z.literal('data-trek-mentions'), data: assistantMentionsSchema }),
|
||||
]);
|
||||
/** The parts of a message, with the change, receipt and card parts read or written as given. */
|
||||
function messagePartSchema(
|
||||
change: z.ZodType<AssistantChangePreview>,
|
||||
applied: z.ZodType<AssistantApplied>,
|
||||
card: z.ZodType<AssistantCard>,
|
||||
) {
|
||||
return z.discriminatedUnion('type', [
|
||||
z.object({ type: z.literal('text'), text: z.string() }),
|
||||
// A file the user handed the assistant. Only its description is kept, never its content.
|
||||
z.object({
|
||||
type: z.literal('attachment'),
|
||||
name: z.string(),
|
||||
mime: z.string(),
|
||||
size: z.number().int().nonnegative(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('tool-call'),
|
||||
toolCallId: z.string(),
|
||||
toolName: z.string(),
|
||||
args: z.record(z.string(), z.unknown()),
|
||||
result: z.unknown().optional(),
|
||||
isError: z.boolean().optional(),
|
||||
approval: assistantToolApprovalSchema.optional(),
|
||||
}),
|
||||
z.object({ type: z.literal('data-trek-change'), data: change }),
|
||||
z.object({ type: z.literal('data-trek-applied'), data: applied }),
|
||||
z.object({ type: z.literal('data-trek-places'), data: assistantPlacesSchema }),
|
||||
z.object({ type: z.literal('data-trek-weather'), data: assistantWeatherSchema }),
|
||||
// The card of one tool call: a read's beside the answer, an applied write's on the message that proposed it.
|
||||
z.object({ type: z.literal('data-trek-card'), data: card }),
|
||||
// Only on a user message.
|
||||
z.object({ type: z.literal('data-trek-mentions'), data: assistantMentionsSchema }),
|
||||
]);
|
||||
}
|
||||
|
||||
/** One stored part of a message, in the order the model produced it, as a reader takes it. */
|
||||
export const assistantMessagePartSchema = messagePartSchema(
|
||||
assistantChangePreviewSchema,
|
||||
assistantAppliedSchema,
|
||||
assistantCardSchema,
|
||||
);
|
||||
export type AssistantMessagePart = z.infer<typeof assistantMessagePartSchema>;
|
||||
|
||||
/** The parts as the server writes them: a verb, entity or card this build cannot name is a bug in the writer. */
|
||||
export const assistantMessagePartWriteSchema = messagePartSchema(
|
||||
assistantChangePreviewWriteSchema,
|
||||
assistantAppliedWriteSchema,
|
||||
assistantCardWriteSchema,
|
||||
);
|
||||
|
||||
/**
|
||||
* A message's parts as they are read back: a `data-trek-*` part this build
|
||||
* does not know, or one that no longer reads, is dropped, so a part a later
|
||||
* build added never blanks the message around it. Any other broken part
|
||||
* still fails the whole list. A known part reads through its tolerant fields
|
||||
* (a later verb, entity or card kind); parts are written strictly, with
|
||||
* `assistantMessagePartWriteSchema`.
|
||||
*/
|
||||
export const assistantStoredPartsSchema = z
|
||||
.array(
|
||||
z.union([
|
||||
assistantMessagePartSchema,
|
||||
z.object({ type: z.string().startsWith('data-trek-') }).transform(() => null),
|
||||
]),
|
||||
)
|
||||
.transform((parts) => parts.filter((part): part is AssistantMessagePart => part !== null));
|
||||
|
||||
export const assistantMessageSchema = z.object({
|
||||
id: z.string(),
|
||||
role: z.enum(['user', 'assistant']),
|
||||
@@ -539,6 +667,7 @@ export const uiStreamChunkSchema = z.discriminatedUnion('type', [
|
||||
z.object({ type: z.literal('data-trek-applied'), data: assistantAppliedSchema }),
|
||||
z.object({ type: z.literal('data-trek-places'), data: assistantPlacesSchema }),
|
||||
z.object({ type: z.literal('data-trek-weather'), data: assistantWeatherSchema }),
|
||||
z.object({ type: z.literal('data-trek-card'), data: assistantCardSchema }),
|
||||
// Live progress of a running step; the message never keeps it.
|
||||
z.object({
|
||||
type: z.literal('data-trek-activity'),
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/** Entries of the bucket list. Only its kind so far; the atlas cards give it its fields. */
|
||||
export const atlasBucketCardSchema = z.object({ kind: z.literal('atlasBucket') });
|
||||
export type AtlasBucketCard = z.infer<typeof atlasBucketCardSchema>;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/** One country of the atlas. Only its kind so far; the atlas cards give it its fields. */
|
||||
export const atlasCountryCardSchema = z.object({ kind: z.literal('atlasCountry') });
|
||||
export type AtlasCountryCard = z.infer<typeof atlasCountryCardSchema>;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* The countries and regions a user has been to. Only its kind so far; the atlas cards give it its
|
||||
* fields.
|
||||
*/
|
||||
export const atlasPassportCardSchema = z.object({ kind: z.literal('atlasPassport') });
|
||||
export type AtlasPassportCard = z.infer<typeof atlasPassportCardSchema>;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { atlasBucketCardSchema } from './atlas-bucket.card';
|
||||
import { atlasCountryCardSchema } from './atlas-country.card';
|
||||
import { atlasPassportCardSchema } from './atlas-passport.card';
|
||||
|
||||
export * from './atlas-bucket.card';
|
||||
export * from './atlas-country.card';
|
||||
export * from './atlas-passport.card';
|
||||
|
||||
/**
|
||||
* What the atlas brings to the assistant chat: the kinds of card it draws,
|
||||
* the verbs of its change cards and what an applied change of its
|
||||
* touches, beyond the core ones. cards/index.ts and assistant.schema.ts
|
||||
* 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 ATLAS_CARD_SCHEMAS = [atlasPassportCardSchema, atlasCountryCardSchema, atlasBucketCardSchema] as const;
|
||||
export const ATLAS_VERB_KEYS = [] as const;
|
||||
export const ATLAS_APPLIED_ENTITIES = [] as const;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Bookings: flights, trains, stays and other reservations. Only its kind so far; the booking cards
|
||||
* give it its fields.
|
||||
*/
|
||||
export const bookingCardSchema = z.object({ kind: z.literal('booking') });
|
||||
export type BookingCard = z.infer<typeof bookingCardSchema>;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { bookingCardSchema } from './booking.card';
|
||||
|
||||
export * from './booking.card';
|
||||
|
||||
/**
|
||||
* What the bookings bring to the assistant chat: the kinds of card they draw,
|
||||
* the verbs of their change cards and what an applied change of theirs
|
||||
* touches, beyond the core ones. cards/index.ts and assistant.schema.ts
|
||||
* 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 BOOKING_CARD_SCHEMAS = [bookingCardSchema] as const;
|
||||
export const BOOKING_VERB_KEYS = [] as const;
|
||||
export const BOOKING_APPLIED_ENTITIES = [] as const;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { costPerPersonCardSchema } from './cost-per-person.card';
|
||||
import { expensesCardSchema } from './expenses.card';
|
||||
import { paymentsCardSchema } from './payments.card';
|
||||
import { settlementCardSchema } from './settlement.card';
|
||||
|
||||
export * from './cost-per-person.card';
|
||||
export * from './expenses.card';
|
||||
export * from './payments.card';
|
||||
export * from './settlement.card';
|
||||
|
||||
/**
|
||||
* What the budget brings to the assistant chat: the kinds of card it draws,
|
||||
* the verbs of its change cards and what an applied change of its
|
||||
* touches, beyond the core ones. cards/index.ts and assistant.schema.ts
|
||||
* 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 BUDGET_CARD_SCHEMAS = [
|
||||
expensesCardSchema,
|
||||
paymentsCardSchema,
|
||||
settlementCardSchema,
|
||||
costPerPersonCardSchema,
|
||||
] as const;
|
||||
export const BUDGET_VERB_KEYS = [] as const;
|
||||
export const BUDGET_APPLIED_ENTITIES = [] as const;
|
||||
@@ -0,0 +1,58 @@
|
||||
import { idSchema } from '../../common/primitives.schema';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* The pieces every card of the assistant chat is built from. A kind uses
|
||||
* these, `idSchema` and its domain's own enums, nothing else, so every card
|
||||
* bounds its text the same way and none of them carries a URL or a path: a
|
||||
* picture travels as a key the client turns into TREK's own address.
|
||||
*/
|
||||
|
||||
/** One line of text, clipped and masked by the server before it is sent. */
|
||||
export const cardText = (max: number) => z.string().min(1).max(max);
|
||||
|
||||
export const cardLatSchema = z.number().min(-90).max(90);
|
||||
export const cardLngSchema = z.number().min(-180).max(180);
|
||||
|
||||
/** A point of a line to draw, `[lat, lng]` at five decimals; single points are `{ lat, lng }` objects. */
|
||||
export const cardPointSchema = z.tuple([cardLatSchema, cardLngSchema]);
|
||||
|
||||
export const cardDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
||||
export const cardClockSchema = z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/);
|
||||
export const cardInstantSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/);
|
||||
|
||||
/** A colour the data carries (a member's, a category's); the client draws it through safeHexColor. */
|
||||
export const cardHexColorSchema = z.string().regex(/^#[0-9a-fA-F]{6}$/);
|
||||
|
||||
export const cardCurrencySchema = z.string().regex(/^[A-Z]{3}$/);
|
||||
export const cardMoneySchema = z.object({ amount: z.number().min(-1e9).max(1e9), currency: cardCurrencySchema });
|
||||
export const cardCountSchema = z.number().int().min(0).max(1_000_000);
|
||||
|
||||
/** A stored file name (an avatar, a place image): the client builds TREK's own path. Never a URL, never a path. */
|
||||
export const cardFileKeySchema = z.string().regex(/^(?!.*\.\.)[A-Za-z0-9][\w.-]{0,199}$/);
|
||||
|
||||
/** A Google place id for TREK's photo proxy, like a found place's photo key without `coords:`. */
|
||||
export const cardPhotoKeySchema = z.string().regex(/^[\w-]{1,200}$/);
|
||||
|
||||
/** A traveller as a card shows them: the client compares `id` with the signed-in user, so no card says "me". */
|
||||
export const cardPersonSchema = z.object({
|
||||
id: idSchema,
|
||||
name: cardText(80),
|
||||
avatarKey: cardFileKeySchema.nullable(),
|
||||
});
|
||||
|
||||
/** A trip day: its id for links, its number or date for words. */
|
||||
export const cardDaySchema = z.object({
|
||||
id: idSchema.nullable(),
|
||||
number: z.number().int().min(1).max(999).nullable(),
|
||||
date: cardDateSchema.nullable(),
|
||||
});
|
||||
|
||||
/** The most one card may weigh, as JSON text; a heavier one is not sent at all. */
|
||||
export const MAX_CARD_CHARS = 24_000;
|
||||
|
||||
export type CardPoint = z.infer<typeof cardPointSchema>;
|
||||
export type CardMoney = z.infer<typeof cardMoneySchema>;
|
||||
export type CardPerson = z.infer<typeof cardPersonSchema>;
|
||||
export type CardDay = z.infer<typeof cardDaySchema>;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Charging stops for an electric car along a drive. Only its kind so far; the road trip cards give it
|
||||
* its fields.
|
||||
*/
|
||||
export const chargingCardSchema = z.object({ kind: z.literal('charging') });
|
||||
export type ChargingCard = z.infer<typeof chargingCardSchema>;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Messages of the trip chat, as the chat shows them. Only its kind so far; the trip chat cards give it
|
||||
* its fields.
|
||||
*/
|
||||
export const collabChatCardSchema = z.object({ kind: z.literal('collabChat') });
|
||||
export type CollabChatCard = z.infer<typeof collabChatCardSchema>;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/** Shared notes of the trip. Only its kind so far; the trip chat cards give it its fields. */
|
||||
export const collabNotesCardSchema = z.object({ kind: z.literal('collabNotes') });
|
||||
export type CollabNotesCard = z.infer<typeof collabNotesCardSchema>;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Polls of the trip chat with their options. Only its kind so far; the trip chat cards give it its
|
||||
* fields.
|
||||
*/
|
||||
export const collabPollsCardSchema = z.object({ kind: z.literal('collabPolls') });
|
||||
export type CollabPollsCard = z.infer<typeof collabPollsCardSchema>;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { collabChatCardSchema } from './collab-chat.card';
|
||||
import { collabNotesCardSchema } from './collab-notes.card';
|
||||
import { collabPollsCardSchema } from './collab-polls.card';
|
||||
|
||||
export * from './collab-chat.card';
|
||||
export * from './collab-notes.card';
|
||||
export * from './collab-polls.card';
|
||||
|
||||
/**
|
||||
* What the trip chat brings to the assistant chat: the kinds of card it draws,
|
||||
* the verbs of its change cards and what an applied change of its
|
||||
* touches, beyond the core ones. cards/index.ts and assistant.schema.ts
|
||||
* 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 COLLAB_CARD_SCHEMAS = [collabChatCardSchema, collabPollsCardSchema, collabNotesCardSchema] as const;
|
||||
export const COLLAB_VERB_KEYS = [] as const;
|
||||
export const COLLAB_APPLIED_ENTITIES = [] as const;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/** The collections a user keeps. Only its kind so far; the collections cards give it its fields. */
|
||||
export const collectionListsCardSchema = z.object({ kind: z.literal('collectionLists') });
|
||||
export type CollectionListsCard = z.infer<typeof collectionListsCardSchema>;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/** Places of a collection. Only its kind so far; the collections cards give it its fields. */
|
||||
export const collectionPlacesCardSchema = z.object({ kind: z.literal('collectionPlaces') });
|
||||
export type CollectionPlacesCard = z.infer<typeof collectionPlacesCardSchema>;
|
||||
@@ -0,0 +1,16 @@
|
||||
import { collectionListsCardSchema } from './collection-lists.card';
|
||||
import { collectionPlacesCardSchema } from './collection-places.card';
|
||||
|
||||
export * from './collection-lists.card';
|
||||
export * from './collection-places.card';
|
||||
|
||||
/**
|
||||
* What the collections bring to the assistant chat: the kinds of card they draw,
|
||||
* the verbs of their change cards and what an applied change of theirs
|
||||
* touches, beyond the core ones. cards/index.ts and assistant.schema.ts
|
||||
* 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 COLLECTION_CARD_SCHEMAS = [collectionPlacesCardSchema, collectionListsCardSchema] as const;
|
||||
export const COLLECTION_VERB_KEYS = [] as const;
|
||||
export const COLLECTION_APPLIED_ENTITIES = [] as const;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* What lies along a drive, within reach of its route. Only its kind so far; the road trip cards give
|
||||
* it its fields.
|
||||
*/
|
||||
export const corridorCardSchema = z.object({ kind: z.literal('corridor') });
|
||||
export type CorridorCard = z.infer<typeof corridorCardSchema>;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/** What the trip costs each traveller. Only its kind so far; the budget cards give it its fields. */
|
||||
export const costPerPersonCardSchema = z.object({ kind: z.literal('costPerPerson') });
|
||||
export type CostPerPersonCard = z.infer<typeof costPerPersonCardSchema>;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/** One day's route between its places. Only its kind so far; the road trip cards give it its fields. */
|
||||
export const dayRouteCardSchema = z.object({ kind: z.literal('dayRoute') });
|
||||
export type DayRouteCard = z.infer<typeof dayRouteCardSchema>;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Expenses of the trip, with who paid and who shares them. Only its kind so far; the budget cards give
|
||||
* it its fields.
|
||||
*/
|
||||
export const expensesCardSchema = z.object({ kind: z.literal('expenses') });
|
||||
export type ExpensesCard = z.infer<typeof expensesCardSchema>;
|
||||
@@ -0,0 +1,209 @@
|
||||
import {
|
||||
ASSISTANT_CHANGE_VERB_KEYS,
|
||||
assistantAppliedSchema,
|
||||
assistantChangePreviewSchema,
|
||||
assistantMessagePartSchema,
|
||||
assistantMessagePartWriteSchema,
|
||||
assistantStoredPartsSchema,
|
||||
assistantTakeProposalRequestSchema,
|
||||
CORE_APPLIED_ENTITIES,
|
||||
CORE_VERB_KEYS,
|
||||
uiStreamChunkSchema,
|
||||
UI_STREAM_DONE,
|
||||
} from '../assistant.schema';
|
||||
import { cardFileKeySchema, cardPersonSchema, cardText, MAX_CARD_CHARS } from './card-parts';
|
||||
import { assistantCardSchema, storedTrekCardSchema, trekCardSchema } from './index';
|
||||
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { z } from 'zod';
|
||||
|
||||
const CALL = 'call_1';
|
||||
const preview = {
|
||||
toolCallId: CALL,
|
||||
verbKey: 'addTour',
|
||||
subject: 'Lake loop',
|
||||
rows: [],
|
||||
dayId: null,
|
||||
placeIds: [],
|
||||
undoable: true,
|
||||
};
|
||||
|
||||
/** Every kind as its package declares it today, built from its own literal. */
|
||||
const KINDS = trekCardSchema.options.map((option) => option.shape.kind.value);
|
||||
|
||||
/**
|
||||
* A kind as the packages build theirs, from the card parts, with bounds to
|
||||
* cross: the read tolerance is the same for every kind, so these cases hold
|
||||
* whatever fields the packages give their own.
|
||||
*/
|
||||
const probeSchema = z
|
||||
.discriminatedUnion('kind', [
|
||||
z.object({ kind: z.literal('probe'), title: cardText(8), people: z.array(cardPersonSchema).max(2) }),
|
||||
])
|
||||
.nullable()
|
||||
.catch(null);
|
||||
|
||||
describe('the card union', () => {
|
||||
it('CARDS-001: holds every kind once, each read as itself', () => {
|
||||
expect(new Set(KINDS).size).toBe(KINDS.length);
|
||||
expect(KINDS).toEqual(expect.arrayContaining(['tourRoute', 'tourList', 'packingList', 'todoList']));
|
||||
for (const kind of KINDS) {
|
||||
const read = storedTrekCardSchema.parse({ kind });
|
||||
if (read !== null) expect(read.kind).toBe(kind);
|
||||
}
|
||||
});
|
||||
|
||||
it('CARDS-002: reads an unknown kind, a missing kind or no card at all as null', () => {
|
||||
for (const card of [{ kind: 'bookPage' }, {}, null, undefined, 'tourRoute', 7]) {
|
||||
expect(storedTrekCardSchema.parse(card)).toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
it('CARDS-003: reads a field past its bound as null, and drops keys a kind does not declare', () => {
|
||||
expect(probeSchema.parse({ kind: 'probe', title: 'Far too long a title', people: [] })).toBeNull();
|
||||
const four = [1, 2, 3, 4].map((id) => ({ id, name: `P${id}`, avatarKey: null }));
|
||||
expect(probeSchema.parse({ kind: 'probe', title: 'Walk', people: four })).toBeNull();
|
||||
expect(probeSchema.parse({ kind: 'probe', title: 'Walk', people: [], url: 'https://x.test' })).toEqual({
|
||||
kind: 'probe',
|
||||
title: 'Walk',
|
||||
people: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('CARDS-004: a stored file key is a file name, never a path, a URL or a way up', () => {
|
||||
expect(cardFileKeySchema.safeParse('a1b2c3.jpg').success).toBe(true);
|
||||
for (const key of ['../secret', 'avatars/a.jpg', 'https://x.test/a.jpg', '.hidden', 'a..b', '']) {
|
||||
expect(cardFileKeySchema.safeParse(key).success).toBe(false);
|
||||
}
|
||||
expect(MAX_CARD_CHARS).toBe(24_000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cards in parts, chunks and previews', () => {
|
||||
it('CARDS-010: a card part reads an unknown, a kindless or a broken card as null and keeps the part', () => {
|
||||
for (const card of [{ kind: 'bookPage', page: 3 }, { title: 'x' }, 'nope']) {
|
||||
const part = assistantMessagePartSchema.parse({ type: 'data-trek-card', data: { toolCallId: CALL, card } });
|
||||
expect(part).toEqual({ type: 'data-trek-card', data: { toolCallId: CALL, card: null } });
|
||||
}
|
||||
});
|
||||
|
||||
it('CARDS-011: a card chunk reads the same way, and a known card comes through with extra keys dropped', () => {
|
||||
const unknown = uiStreamChunkSchema.parse({
|
||||
type: 'data-trek-card',
|
||||
data: { toolCallId: CALL, card: { kind: 'x' } },
|
||||
});
|
||||
expect(unknown).toEqual({ type: 'data-trek-card', data: { toolCallId: CALL, card: null } });
|
||||
const known = assistantCardSchema.parse({ toolCallId: CALL, card: { kind: KINDS[0], later: 1 }, extra: true });
|
||||
expect(known).not.toHaveProperty('extra');
|
||||
if (known.card !== null) expect(known.card).not.toHaveProperty('later');
|
||||
});
|
||||
|
||||
it('CARDS-012: a change preview reads without a card, and with a broken one as null', () => {
|
||||
expect(assistantChangePreviewSchema.parse(preview)).not.toHaveProperty('card');
|
||||
expect(assistantChangePreviewSchema.parse({ ...preview, card: { kind: 'nope' } }).card).toBeNull();
|
||||
expect(assistantChangePreviewSchema.parse({ ...preview, card: { title: 'no kind' } }).card).toBeNull();
|
||||
});
|
||||
|
||||
it('CARDS-014: a verb or an entity a later build added reads as a plain update and as other', () => {
|
||||
const later = { ...preview, verbKey: 'laterVerb' };
|
||||
expect(assistantChangePreviewSchema.parse(later).verbKey).toBe('genericUpdate');
|
||||
const receipt = { toolCallId: CALL, entity: 'later_entity', ids: [4], dayId: null, undoUntil: null };
|
||||
expect(assistantAppliedSchema.parse(receipt).entity).toBe('other');
|
||||
const parts = assistantStoredPartsSchema.parse([
|
||||
{ type: 'data-trek-change', data: later },
|
||||
{ type: 'data-trek-applied', data: receipt },
|
||||
]);
|
||||
expect(parts.map((part) => part.type)).toEqual(['data-trek-change', 'data-trek-applied']);
|
||||
});
|
||||
|
||||
it('CARDS-013: a card is always under its call, whose id is 1 to 64 characters', () => {
|
||||
expect(assistantCardSchema.safeParse({ toolCallId: '', card: null }).success).toBe(false);
|
||||
expect(assistantCardSchema.safeParse({ toolCallId: 'x'.repeat(65), card: null }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stored parts', () => {
|
||||
it('CARDS-020: drop a data part this build does not know, or one that no longer reads, and keep the rest', () => {
|
||||
const parts = assistantStoredPartsSchema.parse([
|
||||
{ type: 'text', text: 'Here is the walk.' },
|
||||
{ type: 'data-trek-book-page', data: { page: 3 } },
|
||||
{ type: 'data-trek-places', data: { toolCallId: CALL } },
|
||||
{ type: 'data-trek-card', data: { toolCallId: CALL, card: { kind: 'future' } } },
|
||||
{ type: 'data-trek-change', data: preview },
|
||||
]);
|
||||
expect(parts.map((part) => part.type)).toEqual(['text', 'data-trek-card', 'data-trek-change']);
|
||||
});
|
||||
|
||||
it('CARDS-021: still refuse a broken part that is not a data part', () => {
|
||||
expect(assistantStoredPartsSchema.safeParse([{ type: 'text', text: 5 }]).success).toBe(false);
|
||||
expect(assistantStoredPartsSchema.safeParse([{ type: 'tool-call' }]).success).toBe(false);
|
||||
});
|
||||
|
||||
it('CARDS-022: are written strictly: an unknown part is no part', () => {
|
||||
expect(assistantMessagePartSchema.safeParse({ type: 'data-trek-book-page', data: {} }).success).toBe(false);
|
||||
expect(assistantMessagePartWriteSchema.safeParse({ type: 'data-trek-book-page', data: {} }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('CARDS-023: are written strictly: no verb, entity or card kind this build does not know', () => {
|
||||
const write = (part: unknown) => assistantMessagePartWriteSchema.safeParse(part).success;
|
||||
expect(write({ type: 'data-trek-change', data: preview })).toBe(true);
|
||||
expect(write({ type: 'data-trek-change', data: { ...preview, verbKey: 'laterVerb' } })).toBe(false);
|
||||
expect(write({ type: 'data-trek-change', data: { ...preview, card: { kind: 'nope' } } })).toBe(false);
|
||||
const receipt = { toolCallId: CALL, entity: 'later_entity', ids: [4], dayId: null, undoUntil: null };
|
||||
expect(write({ type: 'data-trek-applied', data: receipt })).toBe(false);
|
||||
expect(write({ type: 'data-trek-card', data: { toolCallId: CALL, card: { kind: 'nope' } } })).toBe(false);
|
||||
expect(write({ type: 'data-trek-card', data: { toolCallId: CALL, card: null } })).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the enums the packages extend', () => {
|
||||
it('CARDS-030: keep every core verb and entity, the hike verbs among them', () => {
|
||||
expect(ASSISTANT_CHANGE_VERB_KEYS).toEqual(expect.arrayContaining([...CORE_VERB_KEYS]));
|
||||
expect(ASSISTANT_CHANGE_VERB_KEYS).toEqual(expect.arrayContaining(['addTour', 'updateTour', 'importTour']));
|
||||
expect(new Set(ASSISTANT_CHANGE_VERB_KEYS).size).toBe(ASSISTANT_CHANGE_VERB_KEYS.length);
|
||||
for (const entity of CORE_APPLIED_ENTITIES) {
|
||||
expect(assistantAppliedSchema.shape.entity.safeParse(entity).success).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('assistantTakeProposalRequestSchema', () => {
|
||||
it('CARDS-040: names the read whose proposal is taken, with the turn context and nothing else', () => {
|
||||
const ctx = { locale: 'en', timeZone: 'Europe/Berlin' };
|
||||
expect(assistantTakeProposalRequestSchema.parse({ ...ctx, toolCallId: CALL })).toEqual({
|
||||
...ctx,
|
||||
toolCallId: CALL,
|
||||
});
|
||||
expect(assistantTakeProposalRequestSchema.safeParse({ ...ctx, toolCallId: '' }).success).toBe(false);
|
||||
expect(assistantTakeProposalRequestSchema.safeParse({ ...ctx, toolCallId: CALL, args: {} }).success).toBe(false);
|
||||
expect(assistantTakeProposalRequestSchema.safeParse({ toolCallId: CALL }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ui-stream.cards.v1 fixture', () => {
|
||||
const text = readFileSync(resolve(__dirname, '../__fixtures__/ui-stream.cards.v1.txt'), 'utf8');
|
||||
const events = text
|
||||
.split('\n\n')
|
||||
.filter(Boolean)
|
||||
.map((event) => event.slice('data: '.length));
|
||||
const chunks = events.slice(0, -1).map((event) => uiStreamChunkSchema.parse(JSON.parse(event)));
|
||||
|
||||
it('CARDS-050: is a stream of valid chunks closed by [DONE]', () => {
|
||||
expect(events.at(-1)).toBe(UI_STREAM_DONE);
|
||||
expect(chunks.length).toBeGreaterThan(10);
|
||||
});
|
||||
|
||||
it('CARDS-051: carries an applied write card after its receipt, a read card after its answer, a held one in its preview', () => {
|
||||
const types = chunks.map((chunk) => chunk.type);
|
||||
const cards = chunks.flatMap((chunk) => (chunk.type === 'data-trek-card' ? [chunk.data.toolCallId] : []));
|
||||
const applied = chunks.find((chunk) => chunk.type === 'data-trek-applied');
|
||||
const read = chunks.find((chunk) => chunk.type === 'tool-input-start' && chunk.toolName === 'list_tours');
|
||||
expect(applied?.type === 'data-trek-applied' && applied.data.toolCallId).toBe(cards[0]);
|
||||
expect(read?.type === 'tool-input-start' && read.toolCallId).toBe(cards[1]);
|
||||
expect(types.indexOf('data-trek-applied')).toBeLessThan(types.indexOf('data-trek-card'));
|
||||
const change = chunks.find((chunk) => chunk.type === 'data-trek-change');
|
||||
expect(change?.type === 'data-trek-change' && 'card' in change.data).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import { ATLAS_CARD_SCHEMAS } from './atlas.contract';
|
||||
import { BOOKING_CARD_SCHEMAS } from './booking.contract';
|
||||
import { BUDGET_CARD_SCHEMAS } from './budget.contract';
|
||||
import { COLLAB_CARD_SCHEMAS } from './collab.contract';
|
||||
import { COLLECTION_CARD_SCHEMAS } from './collection.contract';
|
||||
import { JOURNEY_CARD_SCHEMAS } from './journey.contract';
|
||||
import { PACKING_CARD_SCHEMAS } from './packing.contract';
|
||||
import { ROADTRIP_CARD_SCHEMAS } from './roadtrip.contract';
|
||||
import { TOUR_CARD_SCHEMAS } from './tour.contract';
|
||||
import { VACAY_CARD_SCHEMAS } from './vacay.contract';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
export * from './card-parts';
|
||||
export * from './atlas.contract';
|
||||
export * from './booking.contract';
|
||||
export * from './budget.contract';
|
||||
export * from './collab.contract';
|
||||
export * from './collection.contract';
|
||||
export * from './journey.contract';
|
||||
export * from './packing.contract';
|
||||
export * from './roadtrip.contract';
|
||||
export * from './tour.contract';
|
||||
export * from './vacay.contract';
|
||||
|
||||
/**
|
||||
* The cards of the assistant chat: one per tool call, drawn beside an answer
|
||||
* (a read's result) or inside a change card (what a held write will do, and
|
||||
* what it did once applied). Each part of TREK owns its kinds in its
|
||||
* `<pkg>.contract.ts`; the server builds every card from the tool's own
|
||||
* answer and checks it against this union before it is sent.
|
||||
*/
|
||||
export const trekCardSchema = z.discriminatedUnion('kind', [
|
||||
...TOUR_CARD_SCHEMAS,
|
||||
...ROADTRIP_CARD_SCHEMAS,
|
||||
...BUDGET_CARD_SCHEMAS,
|
||||
...BOOKING_CARD_SCHEMAS,
|
||||
...COLLAB_CARD_SCHEMAS,
|
||||
...COLLECTION_CARD_SCHEMAS,
|
||||
...ATLAS_CARD_SCHEMAS,
|
||||
...VACAY_CARD_SCHEMAS,
|
||||
...JOURNEY_CARD_SCHEMAS,
|
||||
...PACKING_CARD_SCHEMAS,
|
||||
]);
|
||||
export type TrekCard = z.infer<typeof trekCardSchema>;
|
||||
export type TrekCardKind = TrekCard['kind'];
|
||||
export type TrekCardOf<K extends TrekCardKind> = Extract<TrekCard, { kind: K }>;
|
||||
|
||||
/**
|
||||
* How a stored or streamed card is read. A kind this build does not know, a
|
||||
* missing field or a value past a bound reads as null and the card is left
|
||||
* out, never the message around it: a newer server may write kinds an older
|
||||
* client or a rolled back server has not heard of (verified with zod 4.6.5).
|
||||
* Keys a kind does not declare are dropped, so a field added later reads too.
|
||||
*/
|
||||
export const storedTrekCardSchema = trekCardSchema.nullable().catch(null);
|
||||
|
||||
/** The card of one tool call, under that call's id: a read's, or a held or applied write's. */
|
||||
export const assistantCardSchema = z.object({
|
||||
toolCallId: z.string().min(1).max(64),
|
||||
card: storedTrekCardSchema,
|
||||
});
|
||||
export type AssistantCard = z.infer<typeof assistantCardSchema>;
|
||||
|
||||
/** The same as the server writes it: a card the union refuses is a bug in its builder, never a silent null. */
|
||||
export const assistantCardWriteSchema = assistantCardSchema.extend({ card: trekCardSchema.nullable() });
|
||||
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/** Entries of a travel journal. Only its kind so far; the travel journal cards give it its fields. */
|
||||
export const journeyEntriesCardSchema = z.object({ kind: z.literal('journeyEntries') });
|
||||
export type JourneyEntriesCard = z.infer<typeof journeyEntriesCardSchema>;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { journeyEntriesCardSchema } from './journey-entries.card';
|
||||
|
||||
export * from './journey-entries.card';
|
||||
|
||||
/**
|
||||
* What the travel journal brings to the assistant chat: the kinds of card it draws,
|
||||
* the verbs of its change cards and what an applied change of its
|
||||
* touches, beyond the core ones. cards/index.ts and assistant.schema.ts
|
||||
* 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] as const;
|
||||
export const JOURNEY_VERB_KEYS = [] as const;
|
||||
export const JOURNEY_APPLIED_ENTITIES = [] as const;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* A packing list: the trip's, a tour's or a template's. Only its kind so far; the packing and to-do
|
||||
* cards give it its fields.
|
||||
*/
|
||||
export const packingListCardSchema = z.object({ kind: z.literal('packingList') });
|
||||
export type PackingListCard = z.infer<typeof packingListCardSchema>;
|
||||
@@ -0,0 +1,16 @@
|
||||
import { packingListCardSchema } from './packing-list.card';
|
||||
import { todoListCardSchema } from './todo-list.card';
|
||||
|
||||
export * from './packing-list.card';
|
||||
export * from './todo-list.card';
|
||||
|
||||
/**
|
||||
* What the packing and to-do lists bring to the assistant chat: the kinds of card they draw,
|
||||
* the verbs of their change cards and what an applied change of theirs
|
||||
* touches, beyond the core ones. cards/index.ts and assistant.schema.ts
|
||||
* 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 PACKING_CARD_SCHEMAS = [packingListCardSchema, todoListCardSchema] as const;
|
||||
export const PACKING_VERB_KEYS = [] as const;
|
||||
export const PACKING_APPLIED_ENTITIES = [] as const;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/** Payments between the travellers. Only its kind so far; the budget cards give it its fields. */
|
||||
export const paymentsCardSchema = z.object({ kind: z.literal('payments') });
|
||||
export type PaymentsCard = z.infer<typeof paymentsCardSchema>;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* A road trip's drive with its days and stops. Only its kind so far; the road trip cards give it its
|
||||
* fields.
|
||||
*/
|
||||
export const roadtripCardSchema = z.object({ kind: z.literal('roadtrip') });
|
||||
export type RoadtripCard = z.infer<typeof roadtripCardSchema>;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { chargingCardSchema } from './charging.card';
|
||||
import { corridorCardSchema } from './corridor.card';
|
||||
import { dayRouteCardSchema } from './day-route.card';
|
||||
import { roadtripCardSchema } from './roadtrip.card';
|
||||
import { transitCardSchema } from './transit.card';
|
||||
|
||||
export * from './charging.card';
|
||||
export * from './corridor.card';
|
||||
export * from './day-route.card';
|
||||
export * from './roadtrip.card';
|
||||
export * from './transit.card';
|
||||
|
||||
/**
|
||||
* What the road trip brings to the assistant chat: the kinds of card it draws,
|
||||
* the verbs of its change cards and what an applied change of its
|
||||
* touches, beyond the core ones. cards/index.ts and assistant.schema.ts
|
||||
* 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 ROADTRIP_CARD_SCHEMAS = [
|
||||
roadtripCardSchema,
|
||||
corridorCardSchema,
|
||||
chargingCardSchema,
|
||||
transitCardSchema,
|
||||
dayRouteCardSchema,
|
||||
] as const;
|
||||
export const ROADTRIP_VERB_KEYS = [] as const;
|
||||
export const ROADTRIP_APPLIED_ENTITIES = [] as const;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/** Who pays whom to settle up. Only its kind so far; the budget cards give it its fields. */
|
||||
export const settlementCardSchema = z.object({ kind: z.literal('settlement') });
|
||||
export type SettlementCard = z.infer<typeof settlementCardSchema>;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/** To-dos of the trip. Only its kind so far; the packing and to-do cards give it its fields. */
|
||||
export const todoListCardSchema = z.object({ kind: z.literal('todoList') });
|
||||
export type TodoListCard = z.infer<typeof todoListCardSchema>;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/** The tours of a trip or of one of its days. Only its kind so far; the tours cards give it its fields. */
|
||||
export const tourListCardSchema = z.object({ kind: z.literal('tourList') });
|
||||
export type TourListCard = z.infer<typeof tourListCardSchema>;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* A tour's route: a hike, a city walk, a paddle or a ski plan the assistant proposed, or a saved tour
|
||||
* it read. Only its kind so far; the tours cards give it its fields.
|
||||
*/
|
||||
export const tourRouteCardSchema = z.object({ kind: z.literal('tourRoute') });
|
||||
export type TourRouteCard = z.infer<typeof tourRouteCardSchema>;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { tourListCardSchema } from './tour-list.card';
|
||||
import { tourRouteCardSchema } from './tour-route.card';
|
||||
|
||||
export * from './tour-list.card';
|
||||
export * from './tour-route.card';
|
||||
|
||||
/**
|
||||
* What the tours bring to the assistant chat: the kinds of card they draw,
|
||||
* the verbs of their change cards and what an applied change of theirs
|
||||
* touches, beyond the core ones. cards/index.ts and assistant.schema.ts
|
||||
* 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 TOUR_CARD_SCHEMAS = [tourRouteCardSchema, tourListCardSchema] as const;
|
||||
/** addTour, updateTour and importTour sit in the core list already. */
|
||||
export const TOUR_VERB_KEYS = [] as const;
|
||||
/** 'tour' is a core entity already. */
|
||||
export const TOUR_APPLIED_ENTITIES = [] as const;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* A public transport journey with its legs and times. Only its kind so far; the road trip cards give
|
||||
* it its fields.
|
||||
*/
|
||||
export const transitCardSchema = z.object({ kind: z.literal('transit') });
|
||||
export type TransitCard = z.infer<typeof transitCardSchema>;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Vacation days on a month calendar, with the days left. Only its kind so far; the vacation planner
|
||||
* cards give it its fields.
|
||||
*/
|
||||
export const vacayCardSchema = z.object({ kind: z.literal('vacay') });
|
||||
export type VacayCard = z.infer<typeof vacayCardSchema>;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { vacayCardSchema } from './vacay.card';
|
||||
|
||||
export * from './vacay.card';
|
||||
|
||||
/**
|
||||
* What the vacation planner brings to the assistant chat: the kinds of card it draws,
|
||||
* the verbs of its change cards and what an applied change of its
|
||||
* touches, beyond the core ones. cards/index.ts and assistant.schema.ts
|
||||
* 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 VACAY_CARD_SCHEMAS = [vacayCardSchema] as const;
|
||||
export const VACAY_VERB_KEYS = [] as const;
|
||||
export const VACAY_APPLIED_ENTITIES = [] as const;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantAtlas: TranslationStrings = {};
|
||||
export default assistantAtlas;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantBooking: TranslationStrings = {};
|
||||
export default assistantBooking;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantBudget: TranslationStrings = {};
|
||||
export default assistantBudget;
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantCard: TranslationStrings = {
|
||||
'assistant.card.common.open': 'فتح',
|
||||
'assistant.card.common.more': '{count} أخرى',
|
||||
'assistant.card.common.more.zero': '{count} أخرى',
|
||||
'assistant.card.common.more.one': '{count} أخرى',
|
||||
'assistant.card.common.more.two': '{count} أخرى',
|
||||
'assistant.card.common.more.few': '{count} أخرى',
|
||||
'assistant.card.common.more.many': '{count} أخرى',
|
||||
'assistant.card.common.new': 'جديد',
|
||||
'assistant.card.common.removed': 'أُزيل',
|
||||
'assistant.card.common.changed': 'تغيّر',
|
||||
'assistant.card.common.willDelete': 'سيُحذف',
|
||||
'assistant.card.common.noLonger': 'لم يعد: {names}',
|
||||
'assistant.card.common.you': 'أنت',
|
||||
'assistant.card.common.showDay': 'عرض هذا اليوم في الخطة',
|
||||
};
|
||||
export default assistantCard;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantCollab: TranslationStrings = {};
|
||||
export default assistantCollab;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantCollection: TranslationStrings = {};
|
||||
export default assistantCollection;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantJourney: TranslationStrings = {};
|
||||
export default assistantJourney;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantPacking: TranslationStrings = {};
|
||||
export default assistantPacking;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantRoadtrip: TranslationStrings = {};
|
||||
export default assistantRoadtrip;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantTour: TranslationStrings = {};
|
||||
export default assistantTour;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantVacay: TranslationStrings = {};
|
||||
export default assistantVacay;
|
||||
@@ -1,6 +1,17 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import assistantAtlas from './assistantAtlas';
|
||||
import assistantBooking from './assistantBooking';
|
||||
import assistantBudget from './assistantBudget';
|
||||
import assistantCard from './assistantCard';
|
||||
import assistantCollab from './assistantCollab';
|
||||
import assistantCollection from './assistantCollection';
|
||||
import assistantJourney from './assistantJourney';
|
||||
import assistantPacking from './assistantPacking';
|
||||
import assistantRoadtrip from './assistantRoadtrip';
|
||||
import assistantTour from './assistantTour';
|
||||
import assistantVacay from './assistantVacay';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -118,5 +129,16 @@ const locale = {
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
...assistantCard,
|
||||
...assistantTour,
|
||||
...assistantRoadtrip,
|
||||
...assistantBudget,
|
||||
...assistantBooking,
|
||||
...assistantCollab,
|
||||
...assistantCollection,
|
||||
...assistantAtlas,
|
||||
...assistantVacay,
|
||||
...assistantJourney,
|
||||
...assistantPacking,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantAtlas: TranslationStrings = {};
|
||||
export default assistantAtlas;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantBooking: TranslationStrings = {};
|
||||
export default assistantBooking;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantBudget: TranslationStrings = {};
|
||||
export default assistantBudget;
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantCard: TranslationStrings = {
|
||||
'assistant.card.common.open': 'Aç',
|
||||
'assistant.card.common.more': 'daha {count}',
|
||||
'assistant.card.common.more.one': 'daha {count}',
|
||||
'assistant.card.common.new': 'Yeni',
|
||||
'assistant.card.common.removed': 'Çıxarıldı',
|
||||
'assistant.card.common.changed': 'Dəyişdi',
|
||||
'assistant.card.common.willDelete': 'Silinəcək',
|
||||
'assistant.card.common.noLonger': 'Artıq yoxdur: {names}',
|
||||
'assistant.card.common.you': 'Siz',
|
||||
'assistant.card.common.showDay': 'Bu günü planda göstər',
|
||||
};
|
||||
export default assistantCard;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantCollab: TranslationStrings = {};
|
||||
export default assistantCollab;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantCollection: TranslationStrings = {};
|
||||
export default assistantCollection;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantJourney: TranslationStrings = {};
|
||||
export default assistantJourney;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantPacking: TranslationStrings = {};
|
||||
export default assistantPacking;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantRoadtrip: TranslationStrings = {};
|
||||
export default assistantRoadtrip;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantTour: TranslationStrings = {};
|
||||
export default assistantTour;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantVacay: TranslationStrings = {};
|
||||
export default assistantVacay;
|
||||
@@ -1,6 +1,17 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import assistantAtlas from './assistantAtlas';
|
||||
import assistantBooking from './assistantBooking';
|
||||
import assistantBudget from './assistantBudget';
|
||||
import assistantCard from './assistantCard';
|
||||
import assistantCollab from './assistantCollab';
|
||||
import assistantCollection from './assistantCollection';
|
||||
import assistantJourney from './assistantJourney';
|
||||
import assistantPacking from './assistantPacking';
|
||||
import assistantRoadtrip from './assistantRoadtrip';
|
||||
import assistantTour from './assistantTour';
|
||||
import assistantVacay from './assistantVacay';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -118,5 +129,16 @@ const locale = {
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
...assistantCard,
|
||||
...assistantTour,
|
||||
...assistantRoadtrip,
|
||||
...assistantBudget,
|
||||
...assistantBooking,
|
||||
...assistantCollab,
|
||||
...assistantCollection,
|
||||
...assistantAtlas,
|
||||
...assistantVacay,
|
||||
...assistantJourney,
|
||||
...assistantPacking,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantAtlas: TranslationStrings = {};
|
||||
export default assistantAtlas;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantBooking: TranslationStrings = {};
|
||||
export default assistantBooking;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantBudget: TranslationStrings = {};
|
||||
export default assistantBudget;
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantCard: TranslationStrings = {
|
||||
'assistant.card.common.open': 'Abrir',
|
||||
'assistant.card.common.more': 'mais {count}',
|
||||
'assistant.card.common.more.one': 'mais {count}',
|
||||
'assistant.card.common.new': 'Novo',
|
||||
'assistant.card.common.removed': 'Removido',
|
||||
'assistant.card.common.changed': 'Alterado',
|
||||
'assistant.card.common.willDelete': 'Será excluído',
|
||||
'assistant.card.common.noLonger': 'Não mais: {names}',
|
||||
'assistant.card.common.you': 'Você',
|
||||
'assistant.card.common.showDay': 'Mostrar este dia no plano',
|
||||
};
|
||||
export default assistantCard;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantCollab: TranslationStrings = {};
|
||||
export default assistantCollab;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantCollection: TranslationStrings = {};
|
||||
export default assistantCollection;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantJourney: TranslationStrings = {};
|
||||
export default assistantJourney;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantPacking: TranslationStrings = {};
|
||||
export default assistantPacking;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantRoadtrip: TranslationStrings = {};
|
||||
export default assistantRoadtrip;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantTour: TranslationStrings = {};
|
||||
export default assistantTour;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantVacay: TranslationStrings = {};
|
||||
export default assistantVacay;
|
||||
@@ -1,6 +1,17 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import assistantAtlas from './assistantAtlas';
|
||||
import assistantBooking from './assistantBooking';
|
||||
import assistantBudget from './assistantBudget';
|
||||
import assistantCard from './assistantCard';
|
||||
import assistantCollab from './assistantCollab';
|
||||
import assistantCollection from './assistantCollection';
|
||||
import assistantJourney from './assistantJourney';
|
||||
import assistantPacking from './assistantPacking';
|
||||
import assistantRoadtrip from './assistantRoadtrip';
|
||||
import assistantTour from './assistantTour';
|
||||
import assistantVacay from './assistantVacay';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -118,5 +129,16 @@ const locale = {
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
...assistantCard,
|
||||
...assistantTour,
|
||||
...assistantRoadtrip,
|
||||
...assistantBudget,
|
||||
...assistantBooking,
|
||||
...assistantCollab,
|
||||
...assistantCollection,
|
||||
...assistantAtlas,
|
||||
...assistantVacay,
|
||||
...assistantJourney,
|
||||
...assistantPacking,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantAtlas: TranslationStrings = {};
|
||||
export default assistantAtlas;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantBooking: TranslationStrings = {};
|
||||
export default assistantBooking;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantBudget: TranslationStrings = {};
|
||||
export default assistantBudget;
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantCard: TranslationStrings = {
|
||||
'assistant.card.common.open': 'Obrir',
|
||||
'assistant.card.common.more': '{count} més',
|
||||
'assistant.card.common.more.one': '{count} més',
|
||||
'assistant.card.common.new': 'Nou',
|
||||
'assistant.card.common.removed': 'Tret',
|
||||
'assistant.card.common.changed': 'Canviat',
|
||||
'assistant.card.common.willDelete': "S'eliminarà",
|
||||
'assistant.card.common.noLonger': 'Ja no: {names}',
|
||||
'assistant.card.common.you': 'Tu',
|
||||
'assistant.card.common.showDay': 'Mostra aquest dia al pla',
|
||||
};
|
||||
export default assistantCard;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantCollab: TranslationStrings = {};
|
||||
export default assistantCollab;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantCollection: TranslationStrings = {};
|
||||
export default assistantCollection;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantJourney: TranslationStrings = {};
|
||||
export default assistantJourney;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantPacking: TranslationStrings = {};
|
||||
export default assistantPacking;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantRoadtrip: TranslationStrings = {};
|
||||
export default assistantRoadtrip;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantTour: TranslationStrings = {};
|
||||
export default assistantTour;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantVacay: TranslationStrings = {};
|
||||
export default assistantVacay;
|
||||
@@ -1,6 +1,17 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import assistantAtlas from './assistantAtlas';
|
||||
import assistantBooking from './assistantBooking';
|
||||
import assistantBudget from './assistantBudget';
|
||||
import assistantCard from './assistantCard';
|
||||
import assistantCollab from './assistantCollab';
|
||||
import assistantCollection from './assistantCollection';
|
||||
import assistantJourney from './assistantJourney';
|
||||
import assistantPacking from './assistantPacking';
|
||||
import assistantRoadtrip from './assistantRoadtrip';
|
||||
import assistantTour from './assistantTour';
|
||||
import assistantVacay from './assistantVacay';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -118,5 +129,16 @@ const locale = {
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
...assistantCard,
|
||||
...assistantTour,
|
||||
...assistantRoadtrip,
|
||||
...assistantBudget,
|
||||
...assistantBooking,
|
||||
...assistantCollab,
|
||||
...assistantCollection,
|
||||
...assistantAtlas,
|
||||
...assistantVacay,
|
||||
...assistantJourney,
|
||||
...assistantPacking,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantAtlas: TranslationStrings = {};
|
||||
export default assistantAtlas;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantBooking: TranslationStrings = {};
|
||||
export default assistantBooking;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantBudget: TranslationStrings = {};
|
||||
export default assistantBudget;
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantCard: TranslationStrings = {
|
||||
'assistant.card.common.open': 'Otevřít',
|
||||
'assistant.card.common.more': '{count} dalších',
|
||||
'assistant.card.common.more.one': '{count} další',
|
||||
'assistant.card.common.more.few': '{count} další',
|
||||
'assistant.card.common.new': 'Nové',
|
||||
'assistant.card.common.removed': 'Odebráno',
|
||||
'assistant.card.common.changed': 'Změněno',
|
||||
'assistant.card.common.willDelete': 'Bude smazáno',
|
||||
'assistant.card.common.noLonger': 'Už ne: {names}',
|
||||
'assistant.card.common.you': 'Vy',
|
||||
'assistant.card.common.showDay': 'Zobrazit tento den v plánu',
|
||||
};
|
||||
export default assistantCard;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantCollab: TranslationStrings = {};
|
||||
export default assistantCollab;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantCollection: TranslationStrings = {};
|
||||
export default assistantCollection;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantJourney: TranslationStrings = {};
|
||||
export default assistantJourney;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantPacking: TranslationStrings = {};
|
||||
export default assistantPacking;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantRoadtrip: TranslationStrings = {};
|
||||
export default assistantRoadtrip;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantTour: TranslationStrings = {};
|
||||
export default assistantTour;
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { TranslationStrings } from '../types';
|
||||
|
||||
const assistantVacay: TranslationStrings = {};
|
||||
export default assistantVacay;
|
||||
@@ -1,6 +1,17 @@
|
||||
import admin from './admin';
|
||||
import airport from './airport';
|
||||
import assistant from './assistant';
|
||||
import assistantAtlas from './assistantAtlas';
|
||||
import assistantBooking from './assistantBooking';
|
||||
import assistantBudget from './assistantBudget';
|
||||
import assistantCard from './assistantCard';
|
||||
import assistantCollab from './assistantCollab';
|
||||
import assistantCollection from './assistantCollection';
|
||||
import assistantJourney from './assistantJourney';
|
||||
import assistantPacking from './assistantPacking';
|
||||
import assistantRoadtrip from './assistantRoadtrip';
|
||||
import assistantTour from './assistantTour';
|
||||
import assistantVacay from './assistantVacay';
|
||||
import atlas from './atlas';
|
||||
import backup from './backup';
|
||||
import budget from './budget';
|
||||
@@ -118,5 +129,16 @@ const locale = {
|
||||
...docsync,
|
||||
...ferratas,
|
||||
...assistant,
|
||||
...assistantCard,
|
||||
...assistantTour,
|
||||
...assistantRoadtrip,
|
||||
...assistantBudget,
|
||||
...assistantBooking,
|
||||
...assistantCollab,
|
||||
...assistantCollection,
|
||||
...assistantAtlas,
|
||||
...assistantVacay,
|
||||
...assistantJourney,
|
||||
...assistantPacking,
|
||||
};
|
||||
export default locale;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user