Add context info to server error (#17663)
* fix: Add context info to server error * chore: update webui build output
This commit is contained in:
parent
ed32089927
commit
cee92af553
Binary file not shown.
|
|
@ -575,6 +575,7 @@
|
||||||
|
|
||||||
<DialogChatError
|
<DialogChatError
|
||||||
message={activeErrorDialog?.message ?? ''}
|
message={activeErrorDialog?.message ?? ''}
|
||||||
|
contextInfo={activeErrorDialog?.contextInfo}
|
||||||
onOpenChange={handleErrorDialogOpenChange}
|
onOpenChange={handleErrorDialogOpenChange}
|
||||||
open={Boolean(activeErrorDialog)}
|
open={Boolean(activeErrorDialog)}
|
||||||
type={activeErrorDialog?.type ?? 'server'}
|
type={activeErrorDialog?.type ?? 'server'}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,11 @@
|
||||||
open: boolean;
|
open: boolean;
|
||||||
type: 'timeout' | 'server';
|
type: 'timeout' | 'server';
|
||||||
message: string;
|
message: string;
|
||||||
|
contextInfo?: { n_prompt_tokens: number; n_ctx: number };
|
||||||
onOpenChange?: (open: boolean) => void;
|
onOpenChange?: (open: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { open = $bindable(), type, message, onOpenChange }: Props = $props();
|
let { open = $bindable(), type, message, contextInfo, onOpenChange }: Props = $props();
|
||||||
|
|
||||||
const isTimeout = $derived(type === 'timeout');
|
const isTimeout = $derived(type === 'timeout');
|
||||||
const title = $derived(isTimeout ? 'TCP Timeout' : 'Server Error');
|
const title = $derived(isTimeout ? 'TCP Timeout' : 'Server Error');
|
||||||
|
|
@ -51,6 +52,15 @@
|
||||||
|
|
||||||
<div class={`rounded-lg border px-4 py-3 text-sm ${badgeClass}`}>
|
<div class={`rounded-lg border px-4 py-3 text-sm ${badgeClass}`}>
|
||||||
<p class="font-medium">{message}</p>
|
<p class="font-medium">{message}</p>
|
||||||
|
{#if contextInfo}
|
||||||
|
<div class="mt-2 space-y-1 text-xs opacity-80">
|
||||||
|
<p>
|
||||||
|
<span class="font-medium">Prompt tokens:</span>
|
||||||
|
{contextInfo.n_prompt_tokens.toLocaleString()}
|
||||||
|
</p>
|
||||||
|
<p><span class="font-medium">Context size:</span> {contextInfo.n_ctx.toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AlertDialog.Footer>
|
<AlertDialog.Footer>
|
||||||
|
|
|
||||||
|
|
@ -764,18 +764,33 @@ export class ChatService {
|
||||||
* @param response - HTTP response object
|
* @param response - HTTP response object
|
||||||
* @returns Promise<Error> - Parsed error with context info if available
|
* @returns Promise<Error> - Parsed error with context info if available
|
||||||
*/
|
*/
|
||||||
private static async parseErrorResponse(response: Response): Promise<Error> {
|
private static async parseErrorResponse(
|
||||||
|
response: Response
|
||||||
|
): Promise<Error & { contextInfo?: { n_prompt_tokens: number; n_ctx: number } }> {
|
||||||
try {
|
try {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
const errorData: ApiErrorResponse = JSON.parse(errorText);
|
const errorData: ApiErrorResponse = JSON.parse(errorText);
|
||||||
|
|
||||||
const message = errorData.error?.message || 'Unknown server error';
|
const message = errorData.error?.message || 'Unknown server error';
|
||||||
const error = new Error(message);
|
const error = new Error(message) as Error & {
|
||||||
|
contextInfo?: { n_prompt_tokens: number; n_ctx: number };
|
||||||
|
};
|
||||||
error.name = response.status === 400 ? 'ServerError' : 'HttpError';
|
error.name = response.status === 400 ? 'ServerError' : 'HttpError';
|
||||||
|
|
||||||
|
if (errorData.error && 'n_prompt_tokens' in errorData.error && 'n_ctx' in errorData.error) {
|
||||||
|
error.contextInfo = {
|
||||||
|
n_prompt_tokens: errorData.error.n_prompt_tokens,
|
||||||
|
n_ctx: errorData.error.n_ctx
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
} catch {
|
} catch {
|
||||||
const fallback = new Error(`Server error (${response.status}): ${response.statusText}`);
|
const fallback = new Error(
|
||||||
|
`Server error (${response.status}): ${response.statusText}`
|
||||||
|
) as Error & {
|
||||||
|
contextInfo?: { n_prompt_tokens: number; n_ctx: number };
|
||||||
|
};
|
||||||
fallback.name = 'HttpError';
|
fallback.name = 'HttpError';
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,11 @@ class ChatStore {
|
||||||
|
|
||||||
activeProcessingState = $state<ApiProcessingState | null>(null);
|
activeProcessingState = $state<ApiProcessingState | null>(null);
|
||||||
currentResponse = $state('');
|
currentResponse = $state('');
|
||||||
errorDialogState = $state<{ type: 'timeout' | 'server'; message: string } | null>(null);
|
errorDialogState = $state<{
|
||||||
|
type: 'timeout' | 'server';
|
||||||
|
message: string;
|
||||||
|
contextInfo?: { n_prompt_tokens: number; n_ctx: number };
|
||||||
|
} | null>(null);
|
||||||
isLoading = $state(false);
|
isLoading = $state(false);
|
||||||
chatLoadingStates = new SvelteMap<string, boolean>();
|
chatLoadingStates = new SvelteMap<string, boolean>();
|
||||||
chatStreamingStates = new SvelteMap<string, { response: string; messageId: string }>();
|
chatStreamingStates = new SvelteMap<string, { response: string; messageId: string }>();
|
||||||
|
|
@ -335,8 +339,12 @@ class ChatStore {
|
||||||
return error instanceof Error && (error.name === 'AbortError' || error instanceof DOMException);
|
return error instanceof Error && (error.name === 'AbortError' || error instanceof DOMException);
|
||||||
}
|
}
|
||||||
|
|
||||||
private showErrorDialog(type: 'timeout' | 'server', message: string): void {
|
private showErrorDialog(
|
||||||
this.errorDialogState = { type, message };
|
type: 'timeout' | 'server',
|
||||||
|
message: string,
|
||||||
|
contextInfo?: { n_prompt_tokens: number; n_ctx: number }
|
||||||
|
): void {
|
||||||
|
this.errorDialogState = { type, message, contextInfo };
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissErrorDialog(): void {
|
dismissErrorDialog(): void {
|
||||||
|
|
@ -347,6 +355,23 @@ class ChatStore {
|
||||||
// Message Operations
|
// Message Operations
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a message by ID and optionally validates its role.
|
||||||
|
* Returns message and index, or null if not found or role doesn't match.
|
||||||
|
*/
|
||||||
|
private getMessageByIdWithRole(
|
||||||
|
messageId: string,
|
||||||
|
expectedRole?: ChatRole
|
||||||
|
): { message: DatabaseMessage; index: number } | null {
|
||||||
|
const index = conversationsStore.findMessageIndex(messageId);
|
||||||
|
if (index === -1) return null;
|
||||||
|
|
||||||
|
const message = conversationsStore.activeMessages[index];
|
||||||
|
if (expectedRole && message.role !== expectedRole) return null;
|
||||||
|
|
||||||
|
return { message, index };
|
||||||
|
}
|
||||||
|
|
||||||
async addMessage(
|
async addMessage(
|
||||||
role: ChatRole,
|
role: ChatRole,
|
||||||
content: string,
|
content: string,
|
||||||
|
|
@ -508,7 +533,6 @@ class ChatStore {
|
||||||
) => {
|
) => {
|
||||||
this.stopStreaming();
|
this.stopStreaming();
|
||||||
|
|
||||||
// Build update data - only include model if not already persisted
|
|
||||||
const updateData: Record<string, unknown> = {
|
const updateData: Record<string, unknown> = {
|
||||||
content: finalContent || streamedContent,
|
content: finalContent || streamedContent,
|
||||||
thinking: reasoningContent || streamedReasoningContent,
|
thinking: reasoningContent || streamedReasoningContent,
|
||||||
|
|
@ -520,7 +544,6 @@ class ChatStore {
|
||||||
}
|
}
|
||||||
await DatabaseService.updateMessage(assistantMessage.id, updateData);
|
await DatabaseService.updateMessage(assistantMessage.id, updateData);
|
||||||
|
|
||||||
// Update UI state - always include model and timings if available
|
|
||||||
const idx = conversationsStore.findMessageIndex(assistantMessage.id);
|
const idx = conversationsStore.findMessageIndex(assistantMessage.id);
|
||||||
const uiUpdate: Partial<DatabaseMessage> = {
|
const uiUpdate: Partial<DatabaseMessage> = {
|
||||||
content: updateData.content as string,
|
content: updateData.content as string,
|
||||||
|
|
@ -543,22 +566,38 @@ class ChatStore {
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: Error) => {
|
||||||
this.stopStreaming();
|
this.stopStreaming();
|
||||||
|
|
||||||
if (this.isAbortError(error)) {
|
if (this.isAbortError(error)) {
|
||||||
this.setChatLoading(assistantMessage.convId, false);
|
this.setChatLoading(assistantMessage.convId, false);
|
||||||
this.clearChatStreaming(assistantMessage.convId);
|
this.clearChatStreaming(assistantMessage.convId);
|
||||||
this.clearProcessingState(assistantMessage.convId);
|
this.clearProcessingState(assistantMessage.convId);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error('Streaming error:', error);
|
console.error('Streaming error:', error);
|
||||||
|
|
||||||
this.setChatLoading(assistantMessage.convId, false);
|
this.setChatLoading(assistantMessage.convId, false);
|
||||||
this.clearChatStreaming(assistantMessage.convId);
|
this.clearChatStreaming(assistantMessage.convId);
|
||||||
this.clearProcessingState(assistantMessage.convId);
|
this.clearProcessingState(assistantMessage.convId);
|
||||||
|
|
||||||
const idx = conversationsStore.findMessageIndex(assistantMessage.id);
|
const idx = conversationsStore.findMessageIndex(assistantMessage.id);
|
||||||
|
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
const failedMessage = conversationsStore.removeMessageAtIndex(idx);
|
const failedMessage = conversationsStore.removeMessageAtIndex(idx);
|
||||||
if (failedMessage) DatabaseService.deleteMessage(failedMessage.id).catch(console.error);
|
if (failedMessage) DatabaseService.deleteMessage(failedMessage.id).catch(console.error);
|
||||||
}
|
}
|
||||||
this.showErrorDialog(error.name === 'TimeoutError' ? 'timeout' : 'server', error.message);
|
|
||||||
|
const contextInfo = (
|
||||||
|
error as Error & { contextInfo?: { n_prompt_tokens: number; n_ctx: number } }
|
||||||
|
).contextInfo;
|
||||||
|
|
||||||
|
this.showErrorDialog(
|
||||||
|
error.name === 'TimeoutError' ? 'timeout' : 'server',
|
||||||
|
error.message,
|
||||||
|
contextInfo
|
||||||
|
);
|
||||||
|
|
||||||
if (onError) onError(error);
|
if (onError) onError(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -591,7 +630,9 @@ class ChatStore {
|
||||||
await conversationsStore.updateConversationName(currentConv.id, content.trim());
|
await conversationsStore.updateConversationName(currentConv.id, content.trim());
|
||||||
|
|
||||||
const assistantMessage = await this.createAssistantMessage(userMessage.id);
|
const assistantMessage = await this.createAssistantMessage(userMessage.id);
|
||||||
|
|
||||||
if (!assistantMessage) throw new Error('Failed to create assistant message');
|
if (!assistantMessage) throw new Error('Failed to create assistant message');
|
||||||
|
|
||||||
conversationsStore.addMessageToActive(assistantMessage);
|
conversationsStore.addMessageToActive(assistantMessage);
|
||||||
await this.streamChatCompletion(
|
await this.streamChatCompletion(
|
||||||
conversationsStore.activeMessages.slice(0, -1),
|
conversationsStore.activeMessages.slice(0, -1),
|
||||||
|
|
@ -607,15 +648,26 @@ class ChatStore {
|
||||||
if (!this.errorDialogState) {
|
if (!this.errorDialogState) {
|
||||||
const dialogType =
|
const dialogType =
|
||||||
error instanceof Error && error.name === 'TimeoutError' ? 'timeout' : 'server';
|
error instanceof Error && error.name === 'TimeoutError' ? 'timeout' : 'server';
|
||||||
this.showErrorDialog(dialogType, error instanceof Error ? error.message : 'Unknown error');
|
const contextInfo = (
|
||||||
|
error as Error & { contextInfo?: { n_prompt_tokens: number; n_ctx: number } }
|
||||||
|
).contextInfo;
|
||||||
|
|
||||||
|
this.showErrorDialog(
|
||||||
|
dialogType,
|
||||||
|
error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
contextInfo
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async stopGeneration(): Promise<void> {
|
async stopGeneration(): Promise<void> {
|
||||||
const activeConv = conversationsStore.activeConversation;
|
const activeConv = conversationsStore.activeConversation;
|
||||||
|
|
||||||
if (!activeConv) return;
|
if (!activeConv) return;
|
||||||
|
|
||||||
await this.savePartialResponseIfNeeded(activeConv.id);
|
await this.savePartialResponseIfNeeded(activeConv.id);
|
||||||
|
|
||||||
this.stopStreaming();
|
this.stopStreaming();
|
||||||
this.abortRequest(activeConv.id);
|
this.abortRequest(activeConv.id);
|
||||||
this.setChatLoading(activeConv.id, false);
|
this.setChatLoading(activeConv.id, false);
|
||||||
|
|
@ -655,17 +707,22 @@ class ChatStore {
|
||||||
|
|
||||||
private async savePartialResponseIfNeeded(convId?: string): Promise<void> {
|
private async savePartialResponseIfNeeded(convId?: string): Promise<void> {
|
||||||
const conversationId = convId || conversationsStore.activeConversation?.id;
|
const conversationId = convId || conversationsStore.activeConversation?.id;
|
||||||
|
|
||||||
if (!conversationId) return;
|
if (!conversationId) return;
|
||||||
|
|
||||||
const streamingState = this.chatStreamingStates.get(conversationId);
|
const streamingState = this.chatStreamingStates.get(conversationId);
|
||||||
|
|
||||||
if (!streamingState || !streamingState.response.trim()) return;
|
if (!streamingState || !streamingState.response.trim()) return;
|
||||||
|
|
||||||
const messages =
|
const messages =
|
||||||
conversationId === conversationsStore.activeConversation?.id
|
conversationId === conversationsStore.activeConversation?.id
|
||||||
? conversationsStore.activeMessages
|
? conversationsStore.activeMessages
|
||||||
: await conversationsStore.getConversationMessages(conversationId);
|
: await conversationsStore.getConversationMessages(conversationId);
|
||||||
|
|
||||||
if (!messages.length) return;
|
if (!messages.length) return;
|
||||||
|
|
||||||
const lastMessage = messages[messages.length - 1];
|
const lastMessage = messages[messages.length - 1];
|
||||||
|
|
||||||
if (lastMessage?.role === 'assistant') {
|
if (lastMessage?.role === 'assistant') {
|
||||||
try {
|
try {
|
||||||
const updateData: { content: string; thinking?: string; timings?: ChatMessageTimings } = {
|
const updateData: { content: string; thinking?: string; timings?: ChatMessageTimings } = {
|
||||||
|
|
@ -684,9 +741,13 @@ class ChatStore {
|
||||||
: undefined
|
: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
await DatabaseService.updateMessage(lastMessage.id, updateData);
|
await DatabaseService.updateMessage(lastMessage.id, updateData);
|
||||||
|
|
||||||
lastMessage.content = this.currentResponse;
|
lastMessage.content = this.currentResponse;
|
||||||
|
|
||||||
if (updateData.thinking) lastMessage.thinking = updateData.thinking;
|
if (updateData.thinking) lastMessage.thinking = updateData.thinking;
|
||||||
|
|
||||||
if (updateData.timings) lastMessage.timings = updateData.timings;
|
if (updateData.timings) lastMessage.timings = updateData.timings;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
lastMessage.content = this.currentResponse;
|
lastMessage.content = this.currentResponse;
|
||||||
|
|
@ -700,14 +761,12 @@ class ChatStore {
|
||||||
if (!activeConv) return;
|
if (!activeConv) return;
|
||||||
if (this.isLoading) this.stopGeneration();
|
if (this.isLoading) this.stopGeneration();
|
||||||
|
|
||||||
try {
|
const result = this.getMessageByIdWithRole(messageId, 'user');
|
||||||
const messageIndex = conversationsStore.findMessageIndex(messageId);
|
if (!result) return;
|
||||||
if (messageIndex === -1) return;
|
const { message: messageToUpdate, index: messageIndex } = result;
|
||||||
|
|
||||||
const messageToUpdate = conversationsStore.activeMessages[messageIndex];
|
|
||||||
const originalContent = messageToUpdate.content;
|
const originalContent = messageToUpdate.content;
|
||||||
if (messageToUpdate.role !== 'user') return;
|
|
||||||
|
|
||||||
|
try {
|
||||||
const allMessages = await conversationsStore.getConversationMessages(activeConv.id);
|
const allMessages = await conversationsStore.getConversationMessages(activeConv.id);
|
||||||
const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
|
const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
|
||||||
const isFirstUserMessage = rootMessage && messageToUpdate.parent === rootMessage.id;
|
const isFirstUserMessage = rootMessage && messageToUpdate.parent === rootMessage.id;
|
||||||
|
|
@ -724,7 +783,9 @@ class ChatStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
const messagesToRemove = conversationsStore.activeMessages.slice(messageIndex + 1);
|
const messagesToRemove = conversationsStore.activeMessages.slice(messageIndex + 1);
|
||||||
|
|
||||||
for (const message of messagesToRemove) await DatabaseService.deleteMessage(message.id);
|
for (const message of messagesToRemove) await DatabaseService.deleteMessage(message.id);
|
||||||
|
|
||||||
conversationsStore.sliceActiveMessages(messageIndex + 1);
|
conversationsStore.sliceActiveMessages(messageIndex + 1);
|
||||||
conversationsStore.updateConversationTimestamp();
|
conversationsStore.updateConversationTimestamp();
|
||||||
|
|
||||||
|
|
@ -732,8 +793,11 @@ class ChatStore {
|
||||||
this.clearChatStreaming(activeConv.id);
|
this.clearChatStreaming(activeConv.id);
|
||||||
|
|
||||||
const assistantMessage = await this.createAssistantMessage();
|
const assistantMessage = await this.createAssistantMessage();
|
||||||
|
|
||||||
if (!assistantMessage) throw new Error('Failed to create assistant message');
|
if (!assistantMessage) throw new Error('Failed to create assistant message');
|
||||||
|
|
||||||
conversationsStore.addMessageToActive(assistantMessage);
|
conversationsStore.addMessageToActive(assistantMessage);
|
||||||
|
|
||||||
await conversationsStore.updateCurrentNode(assistantMessage.id);
|
await conversationsStore.updateCurrentNode(assistantMessage.id);
|
||||||
await this.streamChatCompletion(
|
await this.streamChatCompletion(
|
||||||
conversationsStore.activeMessages.slice(0, -1),
|
conversationsStore.activeMessages.slice(0, -1),
|
||||||
|
|
@ -758,12 +822,11 @@ class ChatStore {
|
||||||
const activeConv = conversationsStore.activeConversation;
|
const activeConv = conversationsStore.activeConversation;
|
||||||
if (!activeConv || this.isLoading) return;
|
if (!activeConv || this.isLoading) return;
|
||||||
|
|
||||||
try {
|
const result = this.getMessageByIdWithRole(messageId, 'assistant');
|
||||||
const messageIndex = conversationsStore.findMessageIndex(messageId);
|
if (!result) return;
|
||||||
if (messageIndex === -1) return;
|
const { index: messageIndex } = result;
|
||||||
const messageToRegenerate = conversationsStore.activeMessages[messageIndex];
|
|
||||||
if (messageToRegenerate.role !== 'assistant') return;
|
|
||||||
|
|
||||||
|
try {
|
||||||
const messagesToRemove = conversationsStore.activeMessages.slice(messageIndex);
|
const messagesToRemove = conversationsStore.activeMessages.slice(messageIndex);
|
||||||
for (const message of messagesToRemove) await DatabaseService.deleteMessage(message.id);
|
for (const message of messagesToRemove) await DatabaseService.deleteMessage(message.id);
|
||||||
conversationsStore.sliceActiveMessages(messageIndex);
|
conversationsStore.sliceActiveMessages(messageIndex);
|
||||||
|
|
@ -832,6 +895,7 @@ class ChatStore {
|
||||||
const siblings = allMessages.filter(
|
const siblings = allMessages.filter(
|
||||||
(m) => m.parent === messageToDelete.parent && m.id !== messageId
|
(m) => m.parent === messageToDelete.parent && m.id !== messageId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (siblings.length > 0) {
|
if (siblings.length > 0) {
|
||||||
const latestSibling = siblings.reduce((latest, sibling) =>
|
const latestSibling = siblings.reduce((latest, sibling) =>
|
||||||
sibling.timestamp > latest.timestamp ? sibling : latest
|
sibling.timestamp > latest.timestamp ? sibling : latest
|
||||||
|
|
@ -845,6 +909,7 @@ class ChatStore {
|
||||||
}
|
}
|
||||||
await DatabaseService.deleteMessageCascading(activeConv.id, messageId);
|
await DatabaseService.deleteMessageCascading(activeConv.id, messageId);
|
||||||
await conversationsStore.refreshActiveMessages();
|
await conversationsStore.refreshActiveMessages();
|
||||||
|
|
||||||
conversationsStore.updateConversationTimestamp();
|
conversationsStore.updateConversationTimestamp();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to delete message:', error);
|
console.error('Failed to delete message:', error);
|
||||||
|
|
@ -862,12 +927,12 @@ class ChatStore {
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const activeConv = conversationsStore.activeConversation;
|
const activeConv = conversationsStore.activeConversation;
|
||||||
if (!activeConv || this.isLoading) return;
|
if (!activeConv || this.isLoading) return;
|
||||||
try {
|
|
||||||
const idx = conversationsStore.findMessageIndex(messageId);
|
|
||||||
if (idx === -1) return;
|
|
||||||
const msg = conversationsStore.activeMessages[idx];
|
|
||||||
if (msg.role !== 'assistant') return;
|
|
||||||
|
|
||||||
|
const result = this.getMessageByIdWithRole(messageId, 'assistant');
|
||||||
|
if (!result) return;
|
||||||
|
const { message: msg, index: idx } = result;
|
||||||
|
|
||||||
|
try {
|
||||||
if (shouldBranch) {
|
if (shouldBranch) {
|
||||||
const newMessage = await DatabaseService.createMessageBranch(
|
const newMessage = await DatabaseService.createMessageBranch(
|
||||||
{
|
{
|
||||||
|
|
@ -902,12 +967,12 @@ class ChatStore {
|
||||||
async editUserMessagePreserveResponses(messageId: string, newContent: string): Promise<void> {
|
async editUserMessagePreserveResponses(messageId: string, newContent: string): Promise<void> {
|
||||||
const activeConv = conversationsStore.activeConversation;
|
const activeConv = conversationsStore.activeConversation;
|
||||||
if (!activeConv) return;
|
if (!activeConv) return;
|
||||||
try {
|
|
||||||
const idx = conversationsStore.findMessageIndex(messageId);
|
|
||||||
if (idx === -1) return;
|
|
||||||
const msg = conversationsStore.activeMessages[idx];
|
|
||||||
if (msg.role !== 'user') return;
|
|
||||||
|
|
||||||
|
const result = this.getMessageByIdWithRole(messageId, 'user');
|
||||||
|
if (!result) return;
|
||||||
|
const { message: msg, index: idx } = result;
|
||||||
|
|
||||||
|
try {
|
||||||
await DatabaseService.updateMessage(messageId, {
|
await DatabaseService.updateMessage(messageId, {
|
||||||
content: newContent,
|
content: newContent,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
|
|
@ -916,6 +981,7 @@ class ChatStore {
|
||||||
|
|
||||||
const allMessages = await conversationsStore.getConversationMessages(activeConv.id);
|
const allMessages = await conversationsStore.getConversationMessages(activeConv.id);
|
||||||
const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
|
const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
|
||||||
|
|
||||||
if (rootMessage && msg.parent === rootMessage.id && newContent.trim()) {
|
if (rootMessage && msg.parent === rootMessage.id && newContent.trim()) {
|
||||||
await conversationsStore.updateConversationTitleWithConfirmation(
|
await conversationsStore.updateConversationTitleWithConfirmation(
|
||||||
activeConv.id,
|
activeConv.id,
|
||||||
|
|
@ -932,15 +998,16 @@ class ChatStore {
|
||||||
async editMessageWithBranching(messageId: string, newContent: string): Promise<void> {
|
async editMessageWithBranching(messageId: string, newContent: string): Promise<void> {
|
||||||
const activeConv = conversationsStore.activeConversation;
|
const activeConv = conversationsStore.activeConversation;
|
||||||
if (!activeConv || this.isLoading) return;
|
if (!activeConv || this.isLoading) return;
|
||||||
try {
|
|
||||||
const idx = conversationsStore.findMessageIndex(messageId);
|
|
||||||
if (idx === -1) return;
|
|
||||||
const msg = conversationsStore.activeMessages[idx];
|
|
||||||
if (msg.role !== 'user') return;
|
|
||||||
|
|
||||||
|
const result = this.getMessageByIdWithRole(messageId, 'user');
|
||||||
|
if (!result) return;
|
||||||
|
const { message: msg } = result;
|
||||||
|
|
||||||
|
try {
|
||||||
const allMessages = await conversationsStore.getConversationMessages(activeConv.id);
|
const allMessages = await conversationsStore.getConversationMessages(activeConv.id);
|
||||||
const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
|
const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
|
||||||
const isFirstUserMessage = rootMessage && msg.parent === rootMessage.id;
|
const isFirstUserMessage = rootMessage && msg.parent === rootMessage.id;
|
||||||
|
|
||||||
const parentId = msg.parent || rootMessage?.id;
|
const parentId = msg.parent || rootMessage?.id;
|
||||||
if (!parentId) return;
|
if (!parentId) return;
|
||||||
|
|
||||||
|
|
@ -1034,7 +1101,9 @@ class ChatStore {
|
||||||
|
|
||||||
private async generateResponseForMessage(userMessageId: string): Promise<void> {
|
private async generateResponseForMessage(userMessageId: string): Promise<void> {
|
||||||
const activeConv = conversationsStore.activeConversation;
|
const activeConv = conversationsStore.activeConversation;
|
||||||
|
|
||||||
if (!activeConv) return;
|
if (!activeConv) return;
|
||||||
|
|
||||||
this.errorDialogState = null;
|
this.errorDialogState = null;
|
||||||
this.setChatLoading(activeConv.id, true);
|
this.setChatLoading(activeConv.id, true);
|
||||||
this.clearChatStreaming(activeConv.id);
|
this.clearChatStreaming(activeConv.id);
|
||||||
|
|
@ -1071,26 +1140,30 @@ class ChatStore {
|
||||||
async continueAssistantMessage(messageId: string): Promise<void> {
|
async continueAssistantMessage(messageId: string): Promise<void> {
|
||||||
const activeConv = conversationsStore.activeConversation;
|
const activeConv = conversationsStore.activeConversation;
|
||||||
if (!activeConv || this.isLoading) return;
|
if (!activeConv || this.isLoading) return;
|
||||||
try {
|
|
||||||
const idx = conversationsStore.findMessageIndex(messageId);
|
const result = this.getMessageByIdWithRole(messageId, 'assistant');
|
||||||
if (idx === -1) return;
|
if (!result) return;
|
||||||
const msg = conversationsStore.activeMessages[idx];
|
const { message: msg, index: idx } = result;
|
||||||
if (msg.role !== 'assistant') return;
|
|
||||||
if (this.isChatLoading(activeConv.id)) return;
|
if (this.isChatLoading(activeConv.id)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
this.errorDialogState = null;
|
this.errorDialogState = null;
|
||||||
this.setChatLoading(activeConv.id, true);
|
this.setChatLoading(activeConv.id, true);
|
||||||
this.clearChatStreaming(activeConv.id);
|
this.clearChatStreaming(activeConv.id);
|
||||||
|
|
||||||
const allMessages = await conversationsStore.getConversationMessages(activeConv.id);
|
const allMessages = await conversationsStore.getConversationMessages(activeConv.id);
|
||||||
const dbMessage = allMessages.find((m) => m.id === messageId);
|
const dbMessage = allMessages.find((m) => m.id === messageId);
|
||||||
|
|
||||||
if (!dbMessage) {
|
if (!dbMessage) {
|
||||||
this.setChatLoading(activeConv.id, false);
|
this.setChatLoading(activeConv.id, false);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const originalContent = dbMessage.content;
|
const originalContent = dbMessage.content;
|
||||||
const originalThinking = dbMessage.thinking || '';
|
const originalThinking = dbMessage.thinking || '';
|
||||||
|
|
||||||
const conversationContext = conversationsStore.activeMessages.slice(0, idx);
|
const conversationContext = conversationsStore.activeMessages.slice(0, idx);
|
||||||
const contextWithContinue = [
|
const contextWithContinue = [
|
||||||
...conversationContext,
|
...conversationContext,
|
||||||
|
|
@ -1107,6 +1180,7 @@ class ChatStore {
|
||||||
contextWithContinue,
|
contextWithContinue,
|
||||||
{
|
{
|
||||||
...this.getApiOptions(),
|
...this.getApiOptions(),
|
||||||
|
|
||||||
onChunk: (chunk: string) => {
|
onChunk: (chunk: string) => {
|
||||||
hasReceivedContent = true;
|
hasReceivedContent = true;
|
||||||
appendedContent += chunk;
|
appendedContent += chunk;
|
||||||
|
|
@ -1114,6 +1188,7 @@ class ChatStore {
|
||||||
this.setChatStreaming(msg.convId, fullContent, msg.id);
|
this.setChatStreaming(msg.convId, fullContent, msg.id);
|
||||||
conversationsStore.updateMessageAtIndex(idx, { content: fullContent });
|
conversationsStore.updateMessageAtIndex(idx, { content: fullContent });
|
||||||
},
|
},
|
||||||
|
|
||||||
onReasoningChunk: (reasoningChunk: string) => {
|
onReasoningChunk: (reasoningChunk: string) => {
|
||||||
hasReceivedContent = true;
|
hasReceivedContent = true;
|
||||||
appendedThinking += reasoningChunk;
|
appendedThinking += reasoningChunk;
|
||||||
|
|
@ -1121,6 +1196,7 @@ class ChatStore {
|
||||||
thinking: originalThinking + appendedThinking
|
thinking: originalThinking + appendedThinking
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onTimings: (timings: ChatMessageTimings, promptProgress?: ChatMessagePromptProgress) => {
|
onTimings: (timings: ChatMessageTimings, promptProgress?: ChatMessagePromptProgress) => {
|
||||||
const tokensPerSecond =
|
const tokensPerSecond =
|
||||||
timings?.predicted_ms && timings?.predicted_n
|
timings?.predicted_ms && timings?.predicted_n
|
||||||
|
|
@ -1137,6 +1213,7 @@ class ChatStore {
|
||||||
msg.convId
|
msg.convId
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
onComplete: async (
|
onComplete: async (
|
||||||
finalContent?: string,
|
finalContent?: string,
|
||||||
reasoningContent?: string,
|
reasoningContent?: string,
|
||||||
|
|
@ -1161,6 +1238,7 @@ class ChatStore {
|
||||||
this.clearChatStreaming(msg.convId);
|
this.clearChatStreaming(msg.convId);
|
||||||
this.clearProcessingState(msg.convId);
|
this.clearProcessingState(msg.convId);
|
||||||
},
|
},
|
||||||
|
|
||||||
onError: async (error: Error) => {
|
onError: async (error: Error) => {
|
||||||
if (this.isAbortError(error)) {
|
if (this.isAbortError(error)) {
|
||||||
if (hasReceivedContent && appendedContent) {
|
if (hasReceivedContent && appendedContent) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue