feat(chat): sync multi-role backend APIs
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { arrayOrEmpty, booleanOrFalse, numberOrZero } from "../nullable-defaults";
|
||||
|
||||
export const CharacterCapabilitiesResponseSchema = z
|
||||
.object({
|
||||
chat: booleanOrFalse,
|
||||
privateContent: booleanOrFalse,
|
||||
})
|
||||
.readonly();
|
||||
|
||||
export const CharacterListItemSchema = z
|
||||
.object({
|
||||
id: z.string().min(1),
|
||||
displayName: z.string().min(1),
|
||||
isActive: booleanOrFalse,
|
||||
capabilities: CharacterCapabilitiesResponseSchema,
|
||||
sortOrder: numberOrZero,
|
||||
})
|
||||
.readonly();
|
||||
|
||||
export const CharacterListResponseSchema = z
|
||||
.object({
|
||||
items: arrayOrEmpty(CharacterListItemSchema),
|
||||
defaultCharacterId: z.string().min(1),
|
||||
})
|
||||
.readonly();
|
||||
|
||||
export type CharacterListItem = z.output<typeof CharacterListItemSchema>;
|
||||
export type CharacterListResponseInput = z.input<typeof CharacterListResponseSchema>;
|
||||
export type CharacterListResponse = z.output<typeof CharacterListResponseSchema>;
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./character_list_response";
|
||||
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
ChatPreviewsResponseSchema,
|
||||
ChatSyncRequestSchema,
|
||||
SendMessageRequestSchema,
|
||||
} from "@/data/schemas/chat";
|
||||
|
||||
describe("multi-role chat schemas", () => {
|
||||
it("accepts text or image sends and rejects empty or oversized text", () => {
|
||||
expect(
|
||||
SendMessageRequestSchema.parse({ characterId: "elio", message: "Hi" }),
|
||||
).toMatchObject({ characterId: "elio", message: "Hi" });
|
||||
expect(
|
||||
SendMessageRequestSchema.parse({
|
||||
characterId: "maya-tan",
|
||||
imageId: "image-1",
|
||||
}),
|
||||
).toMatchObject({ characterId: "maya-tan", imageId: "image-1" });
|
||||
expect(() => SendMessageRequestSchema.parse({ characterId: "elio" })).toThrow();
|
||||
expect(() =>
|
||||
SendMessageRequestSchema.parse({
|
||||
characterId: "elio",
|
||||
message: "x".repeat(4001),
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("parses nullable previews and immutable sync messages", () => {
|
||||
const previews = ChatPreviewsResponseSchema.parse({
|
||||
items: [{ characterId: "elio", message: null }],
|
||||
});
|
||||
const sync = ChatSyncRequestSchema.parse({
|
||||
characterId: "elio",
|
||||
messages: [
|
||||
{
|
||||
role: "assistant",
|
||||
content: "Welcome back",
|
||||
timestamp: "2026-07-20T00:00:00.000Z",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(previews.items[0]?.message).toBeNull();
|
||||
expect(Object.isFrozen(previews.items)).toBe(true);
|
||||
expect(Object.isFrozen(sync.messages)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -7,9 +7,11 @@ export * from "./chat_media";
|
||||
export * from "./chat_message";
|
||||
export * from "./chat_payloads";
|
||||
export * from "./request/send_message_request";
|
||||
export * from "./request/chat_sync_request";
|
||||
export * from "./request/unlock_history_request";
|
||||
export * from "./request/unlock_private_request";
|
||||
export * from "./response/chat_history_response";
|
||||
export * from "./response/chat_previews_response";
|
||||
export * from "./response/chat_send_response";
|
||||
export * from "./response/unlock_history_response";
|
||||
export * from "./response/unlock_private_response";
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const ChatSyncMessageSchema = z
|
||||
.object({
|
||||
role: z.enum(["user", "assistant"]),
|
||||
content: z.string(),
|
||||
timestamp: z.string().min(1),
|
||||
})
|
||||
.readonly();
|
||||
|
||||
export const ChatSyncRequestSchema = z
|
||||
.object({
|
||||
characterId: z.string().min(1),
|
||||
messages: z.array(ChatSyncMessageSchema).min(1).readonly(),
|
||||
})
|
||||
.readonly();
|
||||
|
||||
export type ChatSyncRequestInput = z.input<typeof ChatSyncRequestSchema>;
|
||||
export type ChatSyncRequest = z.output<typeof ChatSyncRequestSchema>;
|
||||
@@ -3,5 +3,6 @@
|
||||
*/
|
||||
|
||||
export * from "./send_message_request";
|
||||
export * from "./chat_sync_request";
|
||||
export * from "./unlock_private_request";
|
||||
export * from "./unlock_history_request";
|
||||
|
||||
@@ -4,25 +4,31 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
booleanOrFalse,
|
||||
numberOrZero,
|
||||
stringOrEmpty,
|
||||
} from "../../nullable-defaults";
|
||||
import { booleanOrFalse } from "../../nullable-defaults";
|
||||
|
||||
export const SendMessageRequestSchema = z
|
||||
.object({
|
||||
characterId: z.string().min(1),
|
||||
message: stringOrEmpty,
|
||||
image: stringOrEmpty,
|
||||
imageId: stringOrEmpty,
|
||||
imageThumbUrl: stringOrEmpty,
|
||||
imageMediumUrl: stringOrEmpty,
|
||||
imageOriginalUrl: stringOrEmpty,
|
||||
imageWidth: numberOrZero,
|
||||
imageHeight: numberOrZero,
|
||||
message: z.string().max(4000).optional(),
|
||||
imageId: z.string().min(1).optional(),
|
||||
imageThumbUrl: z.string().min(1).optional(),
|
||||
imageMediumUrl: z.string().min(1).optional(),
|
||||
imageOriginalUrl: z.string().min(1).optional(),
|
||||
imageWidth: z.number().int().nonnegative().optional(),
|
||||
imageHeight: z.number().int().nonnegative().optional(),
|
||||
useWebSocket: booleanOrFalse,
|
||||
})
|
||||
.refine(
|
||||
(value) =>
|
||||
Boolean(value.message?.trim()) ||
|
||||
Boolean(
|
||||
value.imageId ||
|
||||
value.imageThumbUrl ||
|
||||
value.imageMediumUrl ||
|
||||
value.imageOriginalUrl,
|
||||
),
|
||||
{ message: "message or image is required" },
|
||||
)
|
||||
.readonly();
|
||||
|
||||
export type SendMessageRequestInput = z.input<typeof SendMessageRequestSchema>;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { arrayOrEmpty } from "../../nullable-defaults";
|
||||
import { ChatMessageSchema } from "../chat_message";
|
||||
|
||||
export const ChatPreviewItemSchema = z
|
||||
.object({
|
||||
characterId: z.string().min(1),
|
||||
message: ChatMessageSchema.nullable().default(null),
|
||||
})
|
||||
.readonly();
|
||||
|
||||
export const ChatPreviewsResponseSchema = z
|
||||
.object({
|
||||
items: arrayOrEmpty(ChatPreviewItemSchema),
|
||||
})
|
||||
.readonly();
|
||||
|
||||
export type ChatPreviewItem = z.output<typeof ChatPreviewItemSchema>;
|
||||
export type ChatPreviewsResponseInput = z.input<typeof ChatPreviewsResponseSchema>;
|
||||
export type ChatPreviewsResponse = z.output<typeof ChatPreviewsResponseSchema>;
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
export * from "./chat_history_response";
|
||||
export * from "./chat_previews_response";
|
||||
export * from "./chat_send_response";
|
||||
export * from "./unlock_history_response";
|
||||
export * from "./unlock_private_response";
|
||||
|
||||
Reference in New Issue
Block a user