feat: label AI-generated conversation messages

This commit is contained in:
jjbrunton committed 2026-08-08 23:23:26 +01:00
1 parent 3beb8aada2
commit 5ad7f1bf77
10 files changed
+69 -11

No files matched your search

+1
View File
@@ -46,6 +46,7 @@ describe('conversation automation', () => {
expect(third).toMatchObject({ kind: 'human-required', remainingTurns: 0 });
expect(chat.sendChatMessage).toHaveBeenCalledTimes(3);
expect(getSaved()?.automation).toMatchObject({ turnsUsed: 3, status: 'human-required' });
expect(getSaved()?.automation.aiMessageFingerprints).toHaveLength(3);
expect(await service.processConversation(incoming(3))).toBeNull();
});
+2 -1
View File
@@ -40,7 +40,7 @@ function detailFixture(chat = alice) {
return {
...chat,
messages: [
{ sender: 'me', text: 'Hello Alice', timestamp: 1_785_999_900_000, isSelf: true },
{ sender: 'me', text: 'Hello Alice', timestamp: 1_785_999_900_000, isSelf: true, isAi: true },
{ sender: 'alice', text: 'Good to hear from you', timestamp: 1_786_000_000_000, isSelf: false },
],
triggeringPost: {
@@ -136,6 +136,7 @@ describe('ConversationsPage', () => {
expect(profileLink).toHaveAttribute('href', 'https://www.reddit.com/u/alice');
}
expect(screen.getByText('Good to hear from you')).toBeInTheDocument();
expect(screen.getByLabelText('Sent by AI')).toHaveTextContent('AI');
expect(screen.getByRole('link', { name: 'Looking for a chat' })).toHaveAttribute('href', expect.stringContaining('post-1'));
expect(screen.getByRole('img', { name: 'Source post: Looking for a chat' })).toHaveAttribute(
'src',
+1 -1
View File
@@ -574,7 +574,7 @@ describe('OpenRouterClient', () => {
boundaries: [],
askedForMedia: false,
topics: [],
automation: { turnsUsed: 0, status: 'active', lastIncomingKey: null, handoffReason: null },
automation: { turnsUsed: 0, status: 'active', lastIncomingKey: null, handoffReason: null, aiMessageFingerprints: [] },
},
{ kind: 'answer-question', reason: 'their latest message asks a direct question' },
);
+16 -9
View File
@@ -3,6 +3,7 @@ import type { AddressInfo } from 'node:net';
import { afterEach, describe, expect, it, vi } from 'vitest';
import type { WebContext } from '../../src/web/context.js';
import { createConversationsRouter } from '../../src/web/routes/conversations.js';
import { buildConversationState, conversationMessageFingerprint } from '../../src/services/conversation-state.js';
const servers: Array<ReturnType<express.Express['listen']>> = [];
@@ -234,13 +235,14 @@ describe('conversations route', () => {
lastMessageSnippet: 'Hello',
isFromUs: false,
unread: false,
messages: [{
sender: 'alice',
text: 'Hello',
timestamp: 1_786_000_000_000,
isSelf: false,
}],
messages: [
{ sender: 'me', text: 'Automated hello', timestamp: 1_785_999_900_000, isSelf: true },
{ sender: 'alice', text: 'Hello', timestamp: 1_786_000_000_000, isSelf: false },
],
};
const savedState = buildConversationState(conversation.messages);
savedState.automation.turnsUsed = 1;
savedState.automation.aiMessageFingerprints = [conversationMessageFingerprint('Automated hello')];
const refreshTopChats = vi.fn();
const loadConversationDetails = vi.fn();
const context = {
@@ -249,6 +251,7 @@ describe('conversations route', () => {
messages: { getLatestByRecipient: vi.fn().mockReturnValue(undefined) },
},
services: {
conversationState: { get: vi.fn().mockReturnValue(savedState), save: vi.fn() },
chat: {
getSyncStatus: vi.fn().mockReturnValue({
syncing: false,
@@ -270,11 +273,13 @@ describe('conversations route', () => {
const body = await response.json();
expect(body.username).toBe('alice');
expect(body.automation).toEqual({
turnsUsed: 0,
remainingTurns: 3,
turnsUsed: 1,
remainingTurns: 2,
status: 'active',
handoffReason: null,
});
expect(body.messages[0]).toMatchObject({ text: 'Automated hello', isAi: true });
expect(body.messages[1].isAi).toBeUndefined();
expect(refreshTopChats).not.toHaveBeenCalled();
expect(loadConversationDetails).not.toHaveBeenCalled();
});
@@ -310,10 +315,12 @@ describe('conversations route', () => {
const response = await request(app, '/api/conversations/room-alice');
expect(response.status).toBe(200);
expect((await response.json()).triggeringPost).toEqual({
const body = await response.json();
expect(body.triggeringPost).toEqual({
id: 'post-exact', subreddit: 'example', title: 'Exact source post',
url: roomOrigin.postUrl, imageUrl: roomOrigin.postImageUrl, sentAt: 9,
});
expect(body.messages[0]).toMatchObject({ text: 'Hello', isAi: true });
expect(context.repos.messages.getLatestByRecipient).not.toHaveBeenCalled();
});