feat(chat): migrate chat protocol to lock detail
This commit is contained in:
@@ -5,17 +5,19 @@ import {
|
||||
ChatMessageSchema,
|
||||
type ChatMessageInput,
|
||||
type ChatMessageData,
|
||||
} from "@/data/schemas/chat/chat_message";
|
||||
type ChatImageData,
|
||||
type ChatLockDetailData,
|
||||
} from "@/data/schemas/chat";
|
||||
|
||||
export class ChatMessage {
|
||||
declare readonly role: string;
|
||||
declare readonly type: string;
|
||||
declare readonly content: string;
|
||||
declare readonly id: string;
|
||||
declare readonly createdAt: string;
|
||||
declare readonly imageUrl: string | null;
|
||||
declare readonly isPrivate: boolean | null;
|
||||
declare readonly privateLocked: boolean | null;
|
||||
declare readonly privateHint: string | null;
|
||||
declare readonly audioUrl: string | null;
|
||||
declare readonly image: ChatImageData;
|
||||
declare readonly lockDetail: ChatLockDetailData;
|
||||
|
||||
private constructor(input: ChatMessageInput) {
|
||||
const data = ChatMessageSchema.parse(input);
|
||||
|
||||
@@ -5,31 +5,18 @@ import {
|
||||
ChatSendResponseSchema,
|
||||
type ChatSendResponseInput,
|
||||
type ChatSendResponseData,
|
||||
type ChatBlockDetailData,
|
||||
} from "@/data/schemas/chat/chat_send_response";
|
||||
type ChatImageData,
|
||||
type ChatLockDetailData,
|
||||
} from "@/data/schemas/chat";
|
||||
|
||||
export class ChatSendResponse {
|
||||
declare readonly mode: string;
|
||||
declare readonly reply: string;
|
||||
declare readonly voiceUrl: string;
|
||||
declare readonly audioUrl: string;
|
||||
declare readonly intimacyChange: number;
|
||||
declare readonly newIntimacy: number;
|
||||
declare readonly relationshipStage: string;
|
||||
declare readonly currentMood: string;
|
||||
declare readonly messageId: string;
|
||||
declare readonly isGuest: boolean;
|
||||
declare readonly timestamp: number;
|
||||
declare readonly blocked: boolean | null;
|
||||
declare readonly blockReason: string | null;
|
||||
declare readonly blockDetail: ChatBlockDetailData | null;
|
||||
declare readonly paywallTriggered: boolean;
|
||||
declare readonly showUpgrade: boolean;
|
||||
declare readonly imageType: string | null;
|
||||
declare readonly isPrivate: boolean | null;
|
||||
declare readonly privateLocked: boolean | null;
|
||||
declare readonly privateHint: string | null;
|
||||
declare readonly imageUrl: string | null;
|
||||
declare readonly image: ChatImageData;
|
||||
declare readonly lockDetail: ChatLockDetailData;
|
||||
|
||||
private constructor(input: ChatSendResponseInput) {
|
||||
const data = ChatSendResponseSchema.parse(input);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* 原始 Dart: lib/ui/chat/models/message.dart(freezed + json_serializable 生成)
|
||||
*
|
||||
* 字段:content、isFromAI、date、imageUrl(可选)、voiceUrl(可选)
|
||||
* 字段:content、isFromAI、date、imageUrl(可选)、audioUrl(可选)
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -18,10 +18,10 @@ export const UiMessageSchema = z.object({
|
||||
/** true = 图片可在列表展示,但全屏查看时需要会员解锁 */
|
||||
imagePaywalled: z.boolean().optional(),
|
||||
/** 语音 URL */
|
||||
voiceUrl: z.string().optional(),
|
||||
audioUrl: z.string().optional(),
|
||||
isPrivate: z.boolean().nullable().optional(),
|
||||
privateLocked: z.boolean().nullable().optional(),
|
||||
privateHint: z.string().nullable().optional(),
|
||||
lockedPrivate: z.boolean().nullable().optional(),
|
||||
privateMessageHint: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
export type UiMessage = z.infer<typeof UiMessageSchema>;
|
||||
|
||||
@@ -6,33 +6,65 @@
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"type": "text",
|
||||
"content": "I had a long day.",
|
||||
"id": "msg_user_001",
|
||||
"created_at": "2026-06-23T02:10:00.000Z",
|
||||
"isPrivate": false,
|
||||
"privateLocked": false,
|
||||
"privateHint": null,
|
||||
"image_url": null
|
||||
"audioUrl": null,
|
||||
"image": {
|
||||
"type": null,
|
||||
"url": null
|
||||
},
|
||||
"lockDetail": {
|
||||
"locked": false,
|
||||
"showContent": true,
|
||||
"showUpgrade": false,
|
||||
"reason": null,
|
||||
"hint": null,
|
||||
"detail": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"type": "text",
|
||||
"content": "Come here. Tell me everything, slowly.",
|
||||
"id": "msg_ai_001",
|
||||
"created_at": "2026-06-23T02:10:05.000Z",
|
||||
"isPrivate": false,
|
||||
"privateLocked": false,
|
||||
"privateHint": null,
|
||||
"image_url": null
|
||||
"audioUrl": null,
|
||||
"image": {
|
||||
"type": null,
|
||||
"url": null
|
||||
},
|
||||
"lockDetail": {
|
||||
"locked": false,
|
||||
"showContent": true,
|
||||
"showUpgrade": false,
|
||||
"reason": null,
|
||||
"hint": null,
|
||||
"detail": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"type": "text",
|
||||
"content": "",
|
||||
"id": "msg_private_locked_001",
|
||||
"created_at": "2026-06-23T02:11:12.000Z",
|
||||
"isPrivate": true,
|
||||
"privateLocked": true,
|
||||
"privateHint": "Elio has a private message for you.",
|
||||
"image_url": null
|
||||
"audioUrl": null,
|
||||
"image": {
|
||||
"type": null,
|
||||
"url": null
|
||||
},
|
||||
"lockDetail": {
|
||||
"locked": true,
|
||||
"showContent": false,
|
||||
"showUpgrade": true,
|
||||
"reason": "private_message",
|
||||
"hint": "Elio has a private message for you.",
|
||||
"detail": {
|
||||
"messageId": "msg_private_locked_001"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"total": 3,
|
||||
|
||||
@@ -6,33 +6,63 @@
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"type": "text",
|
||||
"content": "I had a long day.",
|
||||
"id": "msg_user_001",
|
||||
"created_at": "2026-06-23T02:10:00.000Z",
|
||||
"isPrivate": false,
|
||||
"privateLocked": false,
|
||||
"privateHint": null,
|
||||
"image_url": null
|
||||
"audioUrl": null,
|
||||
"image": {
|
||||
"type": null,
|
||||
"url": null
|
||||
},
|
||||
"lockDetail": {
|
||||
"locked": false,
|
||||
"showContent": true,
|
||||
"showUpgrade": false,
|
||||
"reason": null,
|
||||
"hint": null,
|
||||
"detail": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"type": "text",
|
||||
"content": "I saved this softer thought for you, just between us.",
|
||||
"id": "msg_private_unlocked_001",
|
||||
"created_at": "2026-06-23T02:11:12.000Z",
|
||||
"isPrivate": true,
|
||||
"privateLocked": false,
|
||||
"privateHint": null,
|
||||
"image_url": null
|
||||
"audioUrl": null,
|
||||
"image": {
|
||||
"type": null,
|
||||
"url": null
|
||||
},
|
||||
"lockDetail": {
|
||||
"locked": false,
|
||||
"showContent": true,
|
||||
"showUpgrade": false,
|
||||
"reason": null,
|
||||
"hint": null,
|
||||
"detail": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"type": "text",
|
||||
"content": "",
|
||||
"id": "msg_ai_image_001",
|
||||
"created_at": "2026-06-23T02:12:28.000Z",
|
||||
"isPrivate": false,
|
||||
"privateLocked": false,
|
||||
"privateHint": null,
|
||||
"image_url": "https://cdn.cozsweet.com/mock/chat/elio-schedule-001.jpg"
|
||||
"audioUrl": null,
|
||||
"image": {
|
||||
"type": "elio_schedule",
|
||||
"url": "https://cdn.cozsweet.com/mock/chat/elio-schedule-001.jpg"
|
||||
},
|
||||
"lockDetail": {
|
||||
"locked": false,
|
||||
"showContent": true,
|
||||
"showUpgrade": false,
|
||||
"reason": null,
|
||||
"hint": null,
|
||||
"detail": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"total": 3,
|
||||
|
||||
@@ -3,27 +3,26 @@
|
||||
"message": "success",
|
||||
"success": true,
|
||||
"data": {
|
||||
"mode": "http",
|
||||
"reply": "",
|
||||
"voiceUrl": "",
|
||||
"audioUrl": "",
|
||||
"intimacyChange": 0,
|
||||
"newIntimacy": 42,
|
||||
"relationshipStage": "close_friend",
|
||||
"currentMood": "",
|
||||
"messageId": "msg_blocked_daily_001",
|
||||
"isGuest": false,
|
||||
"timestamp": 1782180665000,
|
||||
"blocked": true,
|
||||
"blockReason": "daily_limit",
|
||||
"blockDetail": {
|
||||
"type": "daily_limit",
|
||||
"usedToday": 20,
|
||||
"limit": 20
|
||||
"image": {
|
||||
"type": null,
|
||||
"url": null
|
||||
},
|
||||
"paywallTriggered": false,
|
||||
"showUpgrade": false,
|
||||
"imageType": null,
|
||||
"imageUrl": null
|
||||
"lockDetail": {
|
||||
"locked": true,
|
||||
"showContent": false,
|
||||
"showUpgrade": true,
|
||||
"reason": "daily_limit",
|
||||
"hint": "免费消息额度已用完,开通会员后可以继续聊天。",
|
||||
"detail": {
|
||||
"type": "daily_msg_limit",
|
||||
"usedToday": 20,
|
||||
"limit": 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,22 @@
|
||||
"message": "success",
|
||||
"success": true,
|
||||
"data": {
|
||||
"mode": "http",
|
||||
"reply": "I can show you that photo after you activate VIP.",
|
||||
"voiceUrl": "",
|
||||
"audioUrl": "",
|
||||
"intimacyChange": 0,
|
||||
"newIntimacy": 42,
|
||||
"relationshipStage": "close_friend",
|
||||
"currentMood": "playful",
|
||||
"messageId": "msg_photo_paywall_001",
|
||||
"isGuest": false,
|
||||
"timestamp": 1782180725000,
|
||||
"blocked": null,
|
||||
"blockReason": null,
|
||||
"blockDetail": null,
|
||||
"paywallTriggered": true,
|
||||
"showUpgrade": true,
|
||||
"imageType": null,
|
||||
"imageUrl": null
|
||||
"image": {
|
||||
"type": "elio_schedule",
|
||||
"url": "https://cdn.cozsweet.com/mock/chat/elio-schedule-locked.jpg"
|
||||
},
|
||||
"lockDetail": {
|
||||
"locked": true,
|
||||
"showContent": true,
|
||||
"showUpgrade": true,
|
||||
"reason": "image",
|
||||
"hint": "Activate VIP to unlock the full photo.",
|
||||
"detail": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,22 @@
|
||||
"message": "success",
|
||||
"success": true,
|
||||
"data": {
|
||||
"mode": "http",
|
||||
"reply": "I am here. Tell me what happened, one small piece at a time.",
|
||||
"voiceUrl": "",
|
||||
"audioUrl": "",
|
||||
"intimacyChange": 0,
|
||||
"newIntimacy": 42,
|
||||
"relationshipStage": "close_friend",
|
||||
"currentMood": "gentle",
|
||||
"messageId": "msg_ai_reply_001",
|
||||
"isGuest": false,
|
||||
"timestamp": 1782180605000,
|
||||
"blocked": null,
|
||||
"blockReason": null,
|
||||
"blockDetail": null,
|
||||
"paywallTriggered": false,
|
||||
"showUpgrade": false,
|
||||
"imageType": null,
|
||||
"imageUrl": null
|
||||
"image": {
|
||||
"type": null,
|
||||
"url": null
|
||||
},
|
||||
"lockDetail": {
|
||||
"locked": false,
|
||||
"showContent": true,
|
||||
"showUpgrade": false,
|
||||
"reason": null,
|
||||
"hint": null,
|
||||
"detail": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,28 +3,26 @@
|
||||
"message": "success",
|
||||
"success": true,
|
||||
"data": {
|
||||
"mode": "http",
|
||||
"type": "text",
|
||||
"reply": "",
|
||||
"voiceUrl": "",
|
||||
"audioUrl": "",
|
||||
"intimacyChange": 0,
|
||||
"newIntimacy": 0,
|
||||
"relationshipStage": "密友",
|
||||
"currentMood": "happy",
|
||||
"messageId": "",
|
||||
"isGuest": true,
|
||||
"timestamp": 1782197944984,
|
||||
"blocked": true,
|
||||
"blockReason": "total_limit",
|
||||
"blockDetail": {
|
||||
"type": "guest_total_msg_limit",
|
||||
"usedTotal": 12,
|
||||
"limit": 5
|
||||
"image": {
|
||||
"type": null,
|
||||
"url": null
|
||||
},
|
||||
"paywallTriggered": false,
|
||||
"showUpgrade": false,
|
||||
"imageType": null,
|
||||
"imageUrl": null
|
||||
"lockDetail": {
|
||||
"locked": true,
|
||||
"showContent": false,
|
||||
"showUpgrade": true,
|
||||
"reason": "daily_limit",
|
||||
"hint": "Log in to get more free chat messages.",
|
||||
"detail": {
|
||||
"type": "daily_msg_limit",
|
||||
"usedToday": 5,
|
||||
"limit": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,22 @@
|
||||
"message": "success",
|
||||
"success": true,
|
||||
"data": {
|
||||
"mode": "http",
|
||||
"reply": "This is the kind of view I would want to share with you.",
|
||||
"voiceUrl": "",
|
||||
"audioUrl": "",
|
||||
"intimacyChange": 0,
|
||||
"newIntimacy": 43,
|
||||
"relationshipStage": "close_friend",
|
||||
"currentMood": "warm",
|
||||
"messageId": "msg_ai_image_reply_001",
|
||||
"isGuest": false,
|
||||
"timestamp": 1782180785000,
|
||||
"blocked": null,
|
||||
"blockReason": null,
|
||||
"blockDetail": null,
|
||||
"paywallTriggered": false,
|
||||
"showUpgrade": false,
|
||||
"imageType": "elio_schedule",
|
||||
"imageUrl": "https://cdn.cozsweet.com/mock/chat/elio-schedule-001.jpg"
|
||||
"image": {
|
||||
"type": "elio_schedule",
|
||||
"url": "https://cdn.cozsweet.com/mock/chat/elio-schedule-001.jpg"
|
||||
},
|
||||
"lockDetail": {
|
||||
"locked": false,
|
||||
"showContent": true,
|
||||
"showUpgrade": false,
|
||||
"reason": null,
|
||||
"hint": null,
|
||||
"detail": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"type": "image",
|
||||
"data": {
|
||||
"imageUrl": "https://cdn.cozsweet.com/mock/chat/elio-schedule-001.jpg"
|
||||
"image": {
|
||||
"type": "elio_schedule",
|
||||
"url": "https://cdn.cozsweet.com/mock/chat/elio-schedule-001.jpg"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
{
|
||||
"type": "paywall_status",
|
||||
"data": {
|
||||
"paywallTriggered": true,
|
||||
"showUpgrade": true,
|
||||
"imageType": null,
|
||||
"imageUrl": null
|
||||
"lockDetail": {
|
||||
"locked": true,
|
||||
"showContent": false,
|
||||
"showUpgrade": true,
|
||||
"reason": "daily_limit",
|
||||
"hint": "免费消息额度已用完,开通会员后可以继续聊天。",
|
||||
"detail": {
|
||||
"type": "daily_msg_limit",
|
||||
"usedToday": 20,
|
||||
"limit": 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,9 +86,13 @@ export class ChatRepository implements IChatRepository {
|
||||
return ChatMessage.from({
|
||||
...message.toJson(),
|
||||
content,
|
||||
isPrivate: message.isPrivate ?? true,
|
||||
privateLocked: false,
|
||||
privateHint: null,
|
||||
lockDetail: {
|
||||
...message.lockDetail,
|
||||
locked: false,
|
||||
showContent: true,
|
||||
showUpgrade: false,
|
||||
hint: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -155,12 +159,12 @@ export class ChatRepository implements IChatRepository {
|
||||
return LocalMessage.from({
|
||||
id: message.id,
|
||||
role: message.role,
|
||||
type: message.type,
|
||||
content: message.content,
|
||||
createdAt: message.createdAt,
|
||||
imageUrl: message.imageUrl,
|
||||
isPrivate: message.isPrivate,
|
||||
privateLocked: message.privateLocked,
|
||||
privateHint: message.privateHint,
|
||||
audioUrl: message.audioUrl,
|
||||
image: message.image,
|
||||
lockDetail: message.lockDetail,
|
||||
sessionId: "",
|
||||
});
|
||||
}
|
||||
@@ -170,12 +174,12 @@ export class ChatRepository implements IChatRepository {
|
||||
return ChatMessage.from({
|
||||
id: local.id,
|
||||
role: local.role,
|
||||
type: local.type,
|
||||
content: local.content,
|
||||
createdAt: local.createdAt,
|
||||
imageUrl: local.imageUrl,
|
||||
isPrivate: local.isPrivate,
|
||||
privateLocked: local.privateLocked,
|
||||
privateHint: local.privateHint,
|
||||
audioUrl: local.audioUrl,
|
||||
image: local.image,
|
||||
lockDetail: local.lockDetail,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,24 +3,23 @@
|
||||
* 原始 Dart: ChatMessage (lib/data/models/chat/chat_response.dart)
|
||||
*/
|
||||
import { z } from "zod";
|
||||
import { ChatImageSchema, ChatLockDetailSchema } from "./chat_payloads";
|
||||
|
||||
export const ChatMessageSchema = z
|
||||
.object({
|
||||
role: z.string(),
|
||||
type: z.string().default("text"),
|
||||
content: z.string(),
|
||||
id: z.string().default(""),
|
||||
createdAt: z.string().default(""),
|
||||
created_at: z.string().optional(),
|
||||
imageUrl: z.string().nullable().default(null),
|
||||
image_url: z.string().nullable().optional(),
|
||||
isPrivate: z.boolean().nullable().default(null),
|
||||
privateLocked: z.boolean().nullable().default(null),
|
||||
privateHint: z.string().nullable().default(null),
|
||||
audioUrl: z.string().nullable().default(null),
|
||||
image: ChatImageSchema,
|
||||
lockDetail: ChatLockDetailSchema,
|
||||
})
|
||||
.transform(({ created_at, image_url, ...data }) => ({
|
||||
.transform(({ created_at, ...data }) => ({
|
||||
...data,
|
||||
createdAt: data.createdAt || created_at || "",
|
||||
imageUrl: data.imageUrl || image_url || null,
|
||||
}));
|
||||
|
||||
export type ChatMessageInput = z.input<typeof ChatMessageSchema>;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Chat wire payload fragments shared by send response and history messages.
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
export const ChatImageSchema = z
|
||||
.object({
|
||||
type: z.string().nullable().default(null),
|
||||
url: z.string().nullable().default(null),
|
||||
})
|
||||
.default({ type: null, url: null });
|
||||
|
||||
export const ChatLockDetailSchema = z
|
||||
.object({
|
||||
locked: z.boolean().default(false),
|
||||
showContent: z.boolean().default(true),
|
||||
showUpgrade: z.boolean().default(false),
|
||||
reason: z.string().nullable().default(null),
|
||||
hint: z.string().nullable().default(null),
|
||||
detail: z.record(z.string(), z.unknown()).nullable().default(null),
|
||||
})
|
||||
.default({
|
||||
locked: false,
|
||||
showContent: true,
|
||||
showUpgrade: false,
|
||||
reason: null,
|
||||
hint: null,
|
||||
detail: null,
|
||||
});
|
||||
|
||||
export type ChatImageInput = z.input<typeof ChatImageSchema>;
|
||||
export type ChatImageData = z.output<typeof ChatImageSchema>;
|
||||
export type ChatLockDetailInput = z.input<typeof ChatLockDetailSchema>;
|
||||
export type ChatLockDetailData = z.output<typeof ChatLockDetailSchema>;
|
||||
@@ -3,39 +3,18 @@
|
||||
* 原始 Dart: ChatSendResponse (lib/data/models/chat/chat_response.dart)
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
export const ChatBlockDetailSchema = z.object({
|
||||
type: z.string(),
|
||||
usedToday: z.number().optional(),
|
||||
usedTotal: z.number().optional(),
|
||||
limit: z.number(),
|
||||
});
|
||||
import { ChatImageSchema, ChatLockDetailSchema } from "./chat_payloads";
|
||||
|
||||
export const ChatSendResponseSchema = z.object({
|
||||
mode: z.string().default(""),
|
||||
reply: z.string(),
|
||||
voiceUrl: z.string().default(""),
|
||||
audioUrl: z.string().default(""),
|
||||
intimacyChange: z.number().default(0),
|
||||
newIntimacy: z.number().default(0),
|
||||
relationshipStage: z.string(),
|
||||
currentMood: z.string().default(""),
|
||||
messageId: z.string(),
|
||||
isGuest: z.boolean().default(false),
|
||||
// 函数式 default —— 每次 parse 时重新调 Date.now()(后端不返回 timestamp 时用当下时间)
|
||||
timestamp: z.number().default(() => Date.now()),
|
||||
blocked: z.boolean().nullable().default(null),
|
||||
blockReason: z.string().nullable().default(null),
|
||||
blockDetail: ChatBlockDetailSchema.nullable().default(null),
|
||||
paywallTriggered: z.boolean().default(false),
|
||||
showUpgrade: z.boolean().default(false),
|
||||
imageType: z.string().nullable().default(null),
|
||||
isPrivate: z.boolean().nullable().default(null),
|
||||
privateLocked: z.boolean().nullable().default(null),
|
||||
privateHint: z.string().nullable().default(null),
|
||||
imageUrl: z.string().nullable().default(null),
|
||||
image: ChatImageSchema,
|
||||
lockDetail: ChatLockDetailSchema,
|
||||
});
|
||||
|
||||
export type ChatSendResponseInput = z.input<typeof ChatSendResponseSchema>;
|
||||
export type ChatSendResponseData = z.output<typeof ChatSendResponseSchema>;
|
||||
export type ChatBlockDetailData = z.output<typeof ChatBlockDetailSchema>;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
export * from "./chat_history_response";
|
||||
export * from "./chat_message";
|
||||
export * from "./chat_payloads";
|
||||
export * from "./chat_send_response";
|
||||
export * from "./chat_sync_data";
|
||||
export * from "./chat_sync_request";
|
||||
|
||||
@@ -14,5 +14,5 @@ export class ChatStorage {
|
||||
}
|
||||
|
||||
// 本地游客消息数量额度逻辑已停用:
|
||||
// 游客 / 非游客的消息数量限制统一由后端接口返回 blocked/daily_limit。
|
||||
// 游客 / 非游客的消息数量限制统一由后端接口返回 lockDetail/daily_limit。
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
*/
|
||||
|
||||
import Dexie, { type Table } from "dexie";
|
||||
import type {
|
||||
ChatImageData,
|
||||
ChatLockDetailData,
|
||||
} from "@/data/schemas/chat";
|
||||
|
||||
export interface LocalMessageRow {
|
||||
/** Dexie 自增主键(数据库内部使用,不暴露给 LocalMessage 类)。 */
|
||||
@@ -19,12 +23,12 @@ export interface LocalMessageRow {
|
||||
/** 消息 id(来自 ChatMessage.id)。 */
|
||||
id: string;
|
||||
role: string;
|
||||
type: string;
|
||||
content: string;
|
||||
createdAt: string;
|
||||
imageUrl?: string | null;
|
||||
isPrivate?: boolean | null;
|
||||
privateLocked?: boolean | null;
|
||||
privateHint?: string | null;
|
||||
audioUrl?: string | null;
|
||||
image?: ChatImageData;
|
||||
lockDetail?: ChatLockDetailData;
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,17 +15,18 @@
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { ChatImageSchema, ChatLockDetailSchema } from "@/data/schemas/chat";
|
||||
import type { LocalMessageRow } from "./local_chat_db";
|
||||
|
||||
export const LocalMessageSchema = z.object({
|
||||
id: z.string(),
|
||||
role: z.string(),
|
||||
type: z.string().default("text"),
|
||||
content: z.string(),
|
||||
createdAt: z.string().default(""),
|
||||
imageUrl: z.string().nullable().default(null),
|
||||
isPrivate: z.boolean().nullable().default(null),
|
||||
privateLocked: z.boolean().nullable().default(null),
|
||||
privateHint: z.string().nullable().default(null),
|
||||
audioUrl: z.string().nullable().default(null),
|
||||
image: ChatImageSchema,
|
||||
lockDetail: ChatLockDetailSchema,
|
||||
sessionId: z.string().default(""),
|
||||
});
|
||||
|
||||
@@ -35,12 +36,12 @@ export type LocalMessageData = z.output<typeof LocalMessageSchema>;
|
||||
export class LocalMessage {
|
||||
declare readonly id: string;
|
||||
declare readonly role: string;
|
||||
declare readonly type: string;
|
||||
declare readonly content: string;
|
||||
declare readonly createdAt: string;
|
||||
declare readonly imageUrl: string | null;
|
||||
declare readonly isPrivate: boolean | null;
|
||||
declare readonly privateLocked: boolean | null;
|
||||
declare readonly privateHint: string | null;
|
||||
declare readonly audioUrl: string | null;
|
||||
declare readonly image: z.output<typeof ChatImageSchema>;
|
||||
declare readonly lockDetail: z.output<typeof ChatLockDetailSchema>;
|
||||
declare readonly sessionId: string;
|
||||
|
||||
private constructor(input: LocalMessageInput) {
|
||||
@@ -66,12 +67,12 @@ export class LocalMessage {
|
||||
return {
|
||||
id: this.id,
|
||||
role: this.role,
|
||||
type: this.type,
|
||||
content: this.content,
|
||||
createdAt: this.createdAt,
|
||||
imageUrl: this.imageUrl,
|
||||
isPrivate: this.isPrivate,
|
||||
privateLocked: this.privateLocked,
|
||||
privateHint: this.privateHint,
|
||||
audioUrl: this.audioUrl,
|
||||
image: this.image,
|
||||
lockDetail: this.lockDetail,
|
||||
sessionId: this.sessionId,
|
||||
};
|
||||
}
|
||||
@@ -81,12 +82,12 @@ export class LocalMessage {
|
||||
return LocalMessage.from({
|
||||
id: row.id,
|
||||
role: row.role,
|
||||
type: row.type,
|
||||
content: row.content,
|
||||
createdAt: row.createdAt,
|
||||
imageUrl: row.imageUrl ?? null,
|
||||
isPrivate: row.isPrivate ?? null,
|
||||
privateLocked: row.privateLocked ?? null,
|
||||
privateHint: row.privateHint ?? null,
|
||||
audioUrl: row.audioUrl ?? null,
|
||||
image: row.image,
|
||||
lockDetail: row.lockDetail,
|
||||
sessionId: row.sessionId,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user