Skip to content

Commit e150310

Browse files
fix: Fix chat title generation. Also improve how errors are reported
1 parent d46615c commit e150310

File tree

3 files changed

+92
-99
lines changed

3 files changed

+92
-99
lines changed

packages/web/src/actions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ export const sew = async <T>(fn: () => Promise<T>): Promise<T | ServiceError> =>
5959
} catch (e) {
6060
Sentry.captureException(e);
6161
logger.error(e);
62+
63+
if (e instanceof Error) {
64+
return unexpectedError(e.message);
65+
}
66+
6267
return unexpectedError(`An unexpected error occurred. Please try again later.`);
6368
}
6469
}

packages/web/src/app/api/(server)/chat/route.ts

Lines changed: 74 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,21 @@ import { isServiceError } from "@/lib/utils";
1010
import { prisma } from "@/prisma";
1111
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';
1212
import { AnthropicProviderOptions, createAnthropic } from '@ai-sdk/anthropic';
13+
import { createAzure } from '@ai-sdk/azure';
14+
import { createDeepSeek } from '@ai-sdk/deepseek';
1315
import { createGoogleGenerativeAI } from '@ai-sdk/google';
1416
import { createVertex } from '@ai-sdk/google-vertex';
1517
import { createVertexAnthropic } from '@ai-sdk/google-vertex/anthropic';
16-
import { createOpenAI, OpenAIResponsesProviderOptions } from "@ai-sdk/openai";
1718
import { createMistral } from '@ai-sdk/mistral';
18-
import { createXai } from '@ai-sdk/xai';
19+
import { createOpenAI, OpenAIResponsesProviderOptions } from "@ai-sdk/openai";
1920
import { LanguageModelV2 as AISDKLanguageModelV2 } from "@ai-sdk/provider";
21+
import { createXai } from '@ai-sdk/xai';
22+
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
2023
import * as Sentry from "@sentry/nextjs";
2124
import { getTokenFromConfig } from "@sourcebot/crypto";
2225
import { OrgRole } from "@sourcebot/db";
2326
import { createLogger } from "@sourcebot/logger";
2427
import { LanguageModel } from "@sourcebot/schemas/v3/index.type";
25-
import { createAzure } from '@ai-sdk/azure';
26-
import { createDeepSeek } from '@ai-sdk/deepseek';
27-
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
2828
import {
2929
createUIMessageStream,
3030
createUIMessageStreamResponse,
@@ -139,7 +139,6 @@ const chatHandler = ({ messages, id, selectedRepos, languageModelId }: ChatHandl
139139

140140
const { model, providerOptions, headers } = await getAISDKLanguageModelAndOptions(languageModelConfig, org.id);
141141

142-
// @todo: refactor this
143142
if (
144143
messages.length === 1 &&
145144
messages[0].role === "user" &&
@@ -149,15 +148,10 @@ const chatHandler = ({ messages, id, selectedRepos, languageModelId }: ChatHandl
149148
const content = messages[0].parts[0].text;
150149

151150
const title = await generateChatTitle(content, model);
152-
if (title) {
153-
updateChatName({
154-
chatId: id,
155-
name: title,
156-
}, domain);
157-
}
158-
else {
159-
logger.error("Failed to generate chat title.");
160-
}
151+
await updateChatName({
152+
chatId: id,
153+
name: title,
154+
}, domain);
161155
}
162156

163157
const traceId = randomUUID();
@@ -184,86 +178,73 @@ const chatHandler = ({ messages, id, selectedRepos, languageModelId }: ChatHandl
184178
}
185179
}).filter(message => message !== undefined);
186180

187-
try {
188-
const stream = createUIMessageStream<SBChatMessage>({
189-
execute: async ({ writer }) => {
190-
writer.write({
191-
type: 'start',
192-
});
193-
194-
const startTime = new Date();
195-
196-
const researchStream = await createAgentStream({
197-
model,
198-
providerOptions,
199-
headers,
200-
inputMessages: messageHistory,
201-
inputSources: sources,
202-
selectedRepos,
203-
onWriteSource: (source) => {
204-
writer.write({
205-
type: 'data-source',
206-
data: source,
207-
});
208-
},
181+
const stream = createUIMessageStream<SBChatMessage>({
182+
execute: async ({ writer }) => {
183+
writer.write({
184+
type: 'start',
185+
});
186+
187+
const startTime = new Date();
188+
189+
const researchStream = await createAgentStream({
190+
model,
191+
providerOptions,
192+
headers,
193+
inputMessages: messageHistory,
194+
inputSources: sources,
195+
selectedRepos,
196+
onWriteSource: (source) => {
197+
writer.write({
198+
type: 'data-source',
199+
data: source,
200+
});
201+
},
202+
traceId,
203+
});
204+
205+
await mergeStreamAsync(researchStream, writer, {
206+
sendReasoning: true,
207+
sendStart: false,
208+
sendFinish: false,
209+
});
210+
211+
const totalUsage = await researchStream.totalUsage;
212+
213+
writer.write({
214+
type: 'message-metadata',
215+
messageMetadata: {
216+
totalTokens: totalUsage.totalTokens,
217+
totalInputTokens: totalUsage.inputTokens,
218+
totalOutputTokens: totalUsage.outputTokens,
219+
totalResponseTimeMs: new Date().getTime() - startTime.getTime(),
220+
modelName: languageModelConfig.displayName ?? languageModelConfig.model,
209221
traceId,
210-
});
211-
212-
await mergeStreamAsync(researchStream, writer, {
213-
sendReasoning: true,
214-
sendStart: false,
215-
sendFinish: false,
216-
});
217-
218-
const totalUsage = await researchStream.totalUsage;
219-
220-
writer.write({
221-
type: 'message-metadata',
222-
messageMetadata: {
223-
totalTokens: totalUsage.totalTokens,
224-
totalInputTokens: totalUsage.inputTokens,
225-
totalOutputTokens: totalUsage.outputTokens,
226-
totalResponseTimeMs: new Date().getTime() - startTime.getTime(),
227-
modelName: languageModelConfig.displayName ?? languageModelConfig.model,
228-
traceId,
229-
}
230-
})
231-
232-
233-
writer.write({
234-
type: 'finish',
235-
});
236-
},
237-
onError: errorHandler,
238-
originalMessages: messages,
239-
onFinish: async ({ messages }) => {
240-
await updateChatMessages({
241-
chatId: id,
242-
messages
243-
}, domain);
244-
},
245-
});
222+
}
223+
})
246224

247-
return createUIMessageStreamResponse({
248-
stream,
249-
});
250-
} catch (error) {
251-
logger.error(error)
252-
logger.error("Error stack:", error instanceof Error ? error.stack : "No stack trace")
253-
Sentry.captureException(error);
254225

255-
return serviceErrorResponse({
256-
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
257-
errorCode: ErrorCode.UNEXPECTED_ERROR,
258-
message: error instanceof Error ? error.message : "Unknown error",
259-
});
260-
}
226+
writer.write({
227+
type: 'finish',
228+
});
229+
},
230+
onError: errorHandler,
231+
originalMessages: messages,
232+
onFinish: async ({ messages }) => {
233+
await updateChatMessages({
234+
chatId: id,
235+
messages
236+
}, domain);
237+
},
238+
});
239+
240+
return createUIMessageStreamResponse({
241+
stream,
242+
});
261243
}, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true
262244
));
263245

264246
const generateChatTitle = async (message: string, model: AISDKLanguageModelV2) => {
265-
try {
266-
const prompt = `Convert this question into a short topic title (max 50 characters).
247+
const prompt = `Convert this question into a short topic title (max 50 characters).
267248
268249
Rules:
269250
- Do NOT include question words (what, where, how, why, when, which)
@@ -279,17 +260,12 @@ Examples:
279260
280261
User question: ${message}`;
281262

282-
const result = await generateText({
283-
model,
284-
prompt,
285-
maxOutputTokens: 20,
286-
});
263+
const result = await generateText({
264+
model,
265+
prompt,
266+
});
287267

288-
return result.text;
289-
} catch (error) {
290-
logger.error("Error generating summary:", error)
291-
return undefined;
292-
}
268+
return result.text;
293269
}
294270

295271
const getAISDKLanguageModelAndOptions = async (config: LanguageModel, orgId: number): Promise<{

packages/web/src/features/chat/components/chatThread/errorBanner.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
'use client';
22

33
import { Button } from '@/components/ui/button';
4+
import { serviceErrorSchema } from '@/lib/serviceError';
45
import { AlertCircle, X } from "lucide-react";
6+
import { useMemo } from 'react';
57

68
interface ErrorBannerProps {
79
error: Error;
@@ -14,6 +16,16 @@ export const ErrorBanner = ({ error, isVisible, onClose }: ErrorBannerProps) =>
1416
return null;
1517
}
1618

19+
const errorMessage = useMemo(() => {
20+
try {
21+
const errorJson = JSON.parse(error.message);
22+
const serviceError = serviceErrorSchema.parse(errorJson);
23+
return serviceError.message;
24+
} catch {
25+
return error.message;
26+
}
27+
}, [error]);
28+
1729
return (
1830
<div className="bg-red-50 border-b border-red-200 dark:bg-red-950/20 dark:border-red-800">
1931
<div className="max-w-5xl mx-auto px-4 py-3">
@@ -24,7 +36,7 @@ export const ErrorBanner = ({ error, isVisible, onClose }: ErrorBannerProps) =>
2436
Error occurred
2537
</span>
2638
<span className="text-sm text-red-600 dark:text-red-400">
27-
{error.message || "An unexpected error occurred. Please try again."}
39+
{errorMessage}
2840
</span>
2941
</div>
3042
<Button

0 commit comments

Comments
 (0)