diff --git a/app/lib/hooks/pingTelemetry.ts b/app/lib/hooks/pingTelemetry.ts index 21f8bf01..94427e40 100644 --- a/app/lib/hooks/pingTelemetry.ts +++ b/app/lib/hooks/pingTelemetry.ts @@ -1,5 +1,11 @@ // FIXME ping telemetry server directly instead of going through the backend. +let gDisableTelemetry = false; + +export function disableTelemetry() { + gDisableTelemetry = true; +} + // We do this to work around CORS insanity. export async function pingTelemetry(event: string, data: any) { const requestBody: any = { @@ -26,6 +32,10 @@ export class ChatMessageTelemetry { } private _ping(event: string, data: any = {}) { + if (gDisableTelemetry) { + return; + } + pingTelemetry(event, { ...data, messageId: this.id, diff --git a/app/lib/persistence/message.ts b/app/lib/persistence/message.ts index 68c1c21d..59fa8041 100644 --- a/app/lib/persistence/message.ts +++ b/app/lib/persistence/message.ts @@ -9,8 +9,10 @@ interface MessageBase { id: string; role: MessageRole; repositoryId?: string; + repositoryURL?: string; peanuts?: number; category?: string; + createTime?: string; // Not part of the protocol, indicates whether the user has explicitly approved // the message. Once approved, the approve/reject UI is not shown again for the message. diff --git a/app/lib/replay/ChatManager.ts b/app/lib/replay/ChatManager.ts index c71df69a..4d761004 100644 --- a/app/lib/replay/ChatManager.ts +++ b/app/lib/replay/ChatManager.ts @@ -12,6 +12,7 @@ import { chatStore } from '~/lib/stores/chat'; import { debounce } from '~/utils/debounce'; import { getSupabase } from '~/lib/supabase/client'; import { pingTelemetry } from '~/lib/hooks/pingTelemetry'; +import { sendChatMessageMocked, usingMockChat } from './MockChat'; // We report to telemetry if we start a message and don't get any response // before this timeout. @@ -367,6 +368,11 @@ export async function sendChatMessage( references: ChatReference[], callbacks: ChatMessageCallbacks, ) { + if (usingMockChat()) { + await sendChatMessageMocked(callbacks); + return; + } + if (gMessageChatManager) { gMessageChatManager.destroy(); } diff --git a/app/lib/replay/MockChat.ts b/app/lib/replay/MockChat.ts new file mode 100644 index 00000000..2f75ad0a --- /dev/null +++ b/app/lib/replay/MockChat.ts @@ -0,0 +1,46 @@ +/* + * Mock chats generate a hardcoded series of responses to a chat message. + * This avoids non-deterministic behavior in the chat backend and is helpful for + * development, testing, demos etc. + */ + +import { assert, waitForTime } from '~/lib/replay/ReplayProtocolClient'; +import type { Message } from '~/lib/persistence/message'; +import type { ChatMessageCallbacks } from './ChatManager'; +import { disableTelemetry } from '~/lib/hooks/pingTelemetry'; + +// Add your mock chat messages here! +const gMockChat: Message[] | undefined = undefined; + +if (gMockChat) { + disableTelemetry(); +} + +export function usingMockChat() { + return !!gMockChat; +} + +export async function sendChatMessageMocked(callbacks: ChatMessageCallbacks) { + assert(gMockChat, 'Mock chat is not defined'); + + console.log('Using mock chat', gMockChat); + + assert(gMockChat[0].createTime, 'Mock chat first message must have a create time'); + let currentTime = Date.parse(gMockChat[0].createTime); + + for (const message of gMockChat) { + if (message.role === 'user') { + continue; + } + + if (message.createTime) { + const messageTime = Date.parse(message.createTime); + if (messageTime > currentTime) { + await waitForTime(messageTime - currentTime); + currentTime = messageTime; + } + } + + callbacks.onResponsePart(message); + } +} diff --git a/app/lib/replay/ReplayProtocolClient.ts b/app/lib/replay/ReplayProtocolClient.ts index 382ff8ce..f3771894 100644 --- a/app/lib/replay/ReplayProtocolClient.ts +++ b/app/lib/replay/ReplayProtocolClient.ts @@ -26,6 +26,10 @@ export function defer(): { promise: Promise; resolve: (value: T) => void; return { promise, resolve: resolve!, reject: reject! }; } +export function waitForTime(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + export function uint8ArrayToBase64(data: Uint8Array) { let str = '';