Skip to content

Commit 93ad8f8

Browse files
authored
feat: store full databunny conversation (#106)
1 parent 6336ead commit 93ad8f8

File tree

14 files changed

+174
-170
lines changed

14 files changed

+174
-170
lines changed

apps/api/src/agent/core/ai-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class AIService {
3838
context.website.id,
3939
context.website.domain,
4040
'execute_chat',
41-
context.model as 'chat' | 'agent' | 'agent-max'
41+
context.model
4242
);
4343

4444
try {

apps/api/src/agent/core/assistant-orchestrator.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { User } from '@databuddy/auth';
2-
import type { Website } from '@databuddy/shared';
2+
import type { StreamingUpdate } from '@databuddy/shared';
3+
import { createId, type Website } from '@databuddy/shared';
34
import type { AssistantRequestType } from '../../schemas';
4-
import type { StreamingUpdate } from '../utils/stream-utils';
55
import { AIService } from './ai-service';
66
import { AssistantSession, type SessionMetrics } from './assistant-session';
77
import { ConversationRepository } from './conversation-repository';
@@ -30,6 +30,7 @@ export class AssistantOrchestrator {
3030
try {
3131
// Step 1: Generate AI response
3232
const aiResponse = await this.aiService.generateResponse(session);
33+
const aiMessageId = createId();
3334

3435
if (!aiResponse.content) {
3536
session.log('AI response was empty');
@@ -42,12 +43,24 @@ export class AssistantOrchestrator {
4243
];
4344
}
4445

46+
const streamingUpdates: StreamingUpdate[] = [
47+
{
48+
type: 'metadata',
49+
data: {
50+
conversationId: session.getContext().conversationId,
51+
messageId: aiMessageId,
52+
},
53+
},
54+
];
55+
4556
// Step 2: Process the response into streaming updates
46-
const streamingUpdates = await this.responseProcessor.process(
57+
const aiResponseUpdates = await this.responseProcessor.process(
4758
aiResponse.content,
4859
session
4960
);
5061

62+
streamingUpdates.push(...aiResponseUpdates);
63+
5164
// Step 3: Save to database (async, don't block response)
5265
const finalResult = streamingUpdates.at(-1);
5366
if (finalResult) {
@@ -57,6 +70,7 @@ export class AssistantOrchestrator {
5770
this.saveConversationAsync(
5871
session,
5972
aiResponse.content,
73+
aiMessageId,
6074
finalResult,
6175
metrics
6276
);
@@ -85,13 +99,15 @@ export class AssistantOrchestrator {
8599
private async saveConversationAsync(
86100
session: AssistantSession,
87101
aiResponse: AIResponseContent,
102+
messageId: string,
88103
finalResult: StreamingUpdate,
89104
metrics: SessionMetrics
90105
): Promise<void> {
91106
try {
92107
await this.conversationRepo.saveConversation(
93108
session,
94109
aiResponse,
110+
messageId,
95111
finalResult,
96112
metrics
97113
);
@@ -110,15 +126,21 @@ export class AssistantOrchestrator {
110126
try {
111127
const errorAIResponse = {
112128
response_type: 'text' as const,
113-
text_response: errorResponse.content,
129+
text_response:
130+
errorResponse.type === 'error'
131+
? errorResponse.content
132+
: 'Oops! Something unexpected happened. Mind trying that again?',
114133
thinking_steps: [
115134
`Error: ${originalError instanceof Error ? originalError.message : 'Unknown error'}`,
116135
],
117136
};
118137

138+
const messageId = createId('NANOID');
139+
119140
await this.conversationRepo.saveConversation(
120141
session,
121142
errorAIResponse,
143+
messageId,
122144
errorResponse,
123145
metrics
124146
);

apps/api/src/agent/core/assistant-session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface SessionContext {
2121
user: User;
2222
website: Website;
2323
conversationId: string;
24-
model: string;
24+
model: 'chat' | 'agent' | 'agent-max';
2525
}
2626

2727
/**

apps/api/src/agent/core/conversation-repository.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import {
44
assistantMessages,
55
db,
66
} from '@databuddy/db';
7+
import type { StreamingUpdate } from '@databuddy/shared';
78
import { createId } from '@databuddy/shared';
89
import { eq } from 'drizzle-orm';
9-
import type { StreamingUpdate } from '../utils/stream-utils';
1010
import type { AssistantSession, SessionMetrics } from './assistant-session';
1111
import type { AIResponseContent } from './response-processor';
1212

@@ -18,6 +18,7 @@ export class ConversationRepository {
1818
async saveConversation(
1919
session: AssistantSession,
2020
aiResponse: AIResponseContent,
21+
aiMessageId: string,
2122
finalResult: StreamingUpdate,
2223
metrics: SessionMetrics
2324
): Promise<void> {
@@ -35,7 +36,6 @@ export class ConversationRepository {
3536
messages.filter((m) => m.role === 'user').length === 1;
3637

3738
try {
38-
const nowIso = new Date().toISOString();
3939
const userMsg: AssistantMessageInput = {
4040
id: createId('NANOID'),
4141
conversationId: context.conversationId,
@@ -60,11 +60,10 @@ export class ConversationRepository {
6060
totalTokens: null,
6161
debugLogs: null,
6262
metadata: null,
63-
createdAt: nowIso,
6463
};
6564

6665
const assistantMsg: AssistantMessageInput = {
67-
id: createId('NANOID'),
66+
id: aiMessageId,
6867
conversationId: context.conversationId,
6968
role: 'assistant',
7069
content: finalResult.content,
@@ -77,17 +76,12 @@ export class ConversationRepository {
7776
thinkingSteps: aiResponse.thinking_steps ?? null,
7877
hasError: finalResult.type === 'error',
7978
errorMessage: finalResult.type === 'error' ? finalResult.content : null,
80-
upvotes: 0,
81-
downvotes: 0,
82-
feedbackComments: null,
8379
aiResponseTime: metrics.aiResponseTime,
8480
totalProcessingTime: metrics.totalProcessingTime,
8581
promptTokens: metrics.tokenUsage.promptTokens,
8682
completionTokens: metrics.tokenUsage.completionTokens,
8783
totalTokens: metrics.tokenUsage.totalTokens,
8884
debugLogs,
89-
metadata: null,
90-
createdAt: nowIso,
9185
};
9286

9387
const conversationMessages: AssistantMessageInput[] = [

apps/api/src/agent/core/response-processor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { z } from 'zod';
22
import { handleChartResponse } from '../handlers/chart-handler';
33
import { handleMetricResponse } from '../handlers/metric-handler';
44
import type { AIResponseJsonSchema } from '../prompts/agent';
5-
import type { StreamingUpdate } from '../utils/stream-utils';
5+
import type { StreamingUpdate } from '@databuddy/shared';
66
import { generateThinkingSteps } from '../utils/stream-utils';
77
import type { AssistantSession } from './assistant-session';
88

apps/api/src/agent/handlers/chart-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import type { StreamingUpdate } from '@databuddy/shared';
12
import type { z } from 'zod';
23
import type { AIResponseJsonSchema } from '../prompts/agent';
34
import { executeQuery } from '../utils/query-executor';
45
import { validateSQL } from '../utils/sql-validator';
5-
import type { StreamingUpdate } from '../utils/stream-utils';
66

77
const getRandomMessage = (messages: string[]): string =>
88
messages[Math.floor(Math.random() * messages.length)] ||

apps/api/src/agent/handlers/metric-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import type { StreamingUpdate } from '@databuddy/shared';
12
import type { z } from 'zod';
23
import type { AIResponseJsonSchema } from '../prompts/agent';
34
import { executeQuery } from '../utils/query-executor';
45
import { validateSQL } from '../utils/sql-validator';
5-
import type { StreamingUpdate } from '../utils/stream-utils';
66

77
export async function handleMetricResponse(
88
parsedAiJson: z.infer<typeof AIResponseJsonSchema>

apps/api/src/agent/processor.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import type { User } from '@databuddy/auth';
2-
import type { Website } from '@databuddy/shared';
2+
import type { StreamingUpdate, Website } from '@databuddy/shared';
33
import type { AssistantRequestType } from '../schemas';
44
import { AssistantOrchestrator } from './core/assistant-orchestrator';
5-
import type { StreamingUpdate } from './utils/stream-utils';
65

76
/**
87
* Clean, simple processor using object-oriented architecture

apps/api/src/agent/utils/stream-utils.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
1-
export interface StreamingUpdate {
2-
type: 'thinking' | 'progress' | 'complete' | 'error';
3-
content: string;
4-
data?: Record<string, unknown>;
5-
debugInfo?: Record<string, unknown>;
6-
}
1+
import type { StreamingUpdate } from '@databuddy/shared';
72

8-
export function createStreamingResponse(
9-
updates: StreamingUpdate[]
10-
): Response {
3+
export function createStreamingResponse(updates: StreamingUpdate[]): Response {
114
const stream = new ReadableStream({
125
async start(controller) {
136
try {
@@ -43,9 +36,7 @@ function createThinkingStep(step: string): string {
4336
return `🧠 ${step}`;
4437
}
4538

46-
export function generateThinkingSteps(
47-
steps: string[]
48-
): StreamingUpdate[] {
39+
export function generateThinkingSteps(steps: string[]): StreamingUpdate[] {
4940
const updates: StreamingUpdate[] = [];
5041
for (const step of steps) {
5142
updates.push({ type: 'thinking', content: createThinkingStep(step) });

apps/api/src/routes/assistant.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import { auth, type User, websitesApi } from '@databuddy/auth';
2+
import type { StreamingUpdate } from '@databuddy/shared';
23
import { Elysia } from 'elysia';
34
import { processAssistantRequest } from '../agent/processor';
4-
import {
5-
createStreamingResponse,
6-
type StreamingUpdate,
7-
} from '../agent/utils/stream-utils';
5+
import { createStreamingResponse } from '../agent/utils/stream-utils';
86
import { validateWebsite } from '../lib/website-utils';
97
import { AssistantRequestSchema, type AssistantRequestType } from '../schemas';
108

0 commit comments

Comments
 (0)