feat: persist settings and enrich conversation context

This commit is contained in:
jjbrunton committed 2026-08-08 18:18:28 +01:00
1 parent a36723a325
commit 2c100d72b6
84 files changed
+2248 -644

No files matched your search

+2
View File
@@ -15,6 +15,8 @@ export const ActivityItemSchema = z.strictObject({
message: z.string(),
recipient: z.string().optional(),
imageUrl: z.string().optional(),
postTitle: z.string().optional(),
postUrl: z.string().optional(),
});
export const ActivityResponseSchema = z.array(ActivityItemSchema);
+2
View File
@@ -24,6 +24,7 @@ export const ActiveChatSchema = z.strictObject({
roomId: z.string().optional(),
messages: z.array(ChatMessageSchema).optional(),
originMessage: z.string().optional(),
hasReceivedImages: z.boolean().optional(),
});
export const ChatsResponseSchema = z.array(ActiveChatSchema);
export const ConversationsResponseSchema = ChatsResponseSchema;
@@ -62,6 +63,7 @@ export const ConversationDetailResponseSchema = ActiveChatSchema.extend({
subreddit: z.string().optional(),
title: z.string().optional(),
url: z.string(),
imageUrl: z.string().nullable().optional(),
sentAt: z.number(),
}).nullable(),
syncInProgress: z.strictObject({
+3
View File
@@ -35,17 +35,20 @@ export const SubredditConfigSchema = z.strictObject({
export const ConfigResponseSchema = RuntimeConfigSchema.extend({
subreddits: z.array(SubredditConfigSchema),
tones: z.array(ToneKeySchema),
advanced: z.record(z.string(), z.unknown()).optional(),
});
export const ConfigUpdateRequestSchema = z.strictObject({
rateLimit: RateLimitConfigSchema.partial().optional(),
browser: z.strictObject({ dryRun: z.boolean().optional() }).optional(),
reddit: RuntimeRedditConfigSchema.partial().optional(),
subreddits: z.array(SubredditConfigSchema).min(1).optional(),
advanced: z.record(z.string(), z.unknown()).optional(),
});
export const ConfigUpdateResponseSchema = z.strictObject({
success: z.literal(true),
config: RuntimeConfigSchema,
subreddits: z.array(SubredditConfigSchema),
advanced: z.record(z.string(), z.unknown()).optional(),
});
export type RuntimeConfig = z.infer<typeof RuntimeConfigSchema>;
+2
View File
@@ -13,6 +13,7 @@ export const SentMessageSchema = z.strictObject({
chatRoomId: z.string().nullable(),
subreddit: z.string().optional(),
postTitle: z.string().optional(),
postUrl: z.string().optional(),
postImageUrl: z.string().optional(),
postImageAnalysis: z.string().optional(),
});
@@ -23,6 +24,7 @@ export const ProcessedPostSchema = z.strictObject({
subreddit: z.string(),
author: z.string(),
title: z.string(),
postUrl: z.string().nullable().optional(),
imageUrl: z.string().nullable(),
imageAnalysis: z.string().nullable(),
generatedMessage: z.string().nullable(),
@@ -106,6 +106,7 @@ describe('dashboard endpoint contracts', () => {
const processedPost = {
id: 'post-1', subreddit: 'example', author: 'alice', title: 'Example',
imageUrl: null, imageAnalysis: null, generatedMessage: 'Hello',
postUrl: 'https://www.reddit.com/r/example/comments/post-1/',
processedAt: 1_700_000_000_000, messageSent: true, error: null,
};
expect(ProcessedPostsResponseSchema.safeParse([processedPost]).success).toBe(true);
@@ -136,7 +137,7 @@ describe('dashboard endpoint contracts', () => {
expect(ConfigResponseSchema.parse(payload)).toEqual(payload);
expect(ConfigResponseSchema.safeParse({ ...payload, tones: [1] }).success).toBe(false);
expect(ConfigUpdateRequestSchema.safeParse({
rateLimit: { maxMessagesPerHour: 10 }, browser: { dryRun: true },
rateLimit: { maxMessagesPerHour: 10 }, browser: { dryRun: true }, advanced: { media: { syncEnabled: false } },
}).success).toBe(true);
expect(ConfigUpdateRequestSchema.safeParse({ browser: { dryRun: 'yes' } }).success).toBe(false);
});
@@ -160,7 +161,7 @@ describe('dashboard endpoint contracts', () => {
...chat,
triggeringPost: {
id: 'post-1', subreddit: 'example', title: 'Example',
url: 'https://www.reddit.com/r/example/comments/post-1/', sentAt: 1_700_000_000_000,
url: 'https://www.reddit.com/r/example/comments/post-1/', imageUrl: 'https://i.redd.it/example.jpg', sentAt: 1_700_000_000_000,
},
syncInProgress: null,
}).success).toBe(true);