diff --git a/apps/backend/src/services/agent.ts b/apps/backend/src/services/agent.ts index e6c2358ff..00c6c80dd 100644 --- a/apps/backend/src/services/agent.ts +++ b/apps/backend/src/services/agent.ts @@ -332,26 +332,26 @@ class AgentManager { } private async _prepareStep(messages: ModelMessage[]): Promise<{ messages: ModelMessage[] }> { - // await compactionService.compactConversationIfNeeded({ - // chat: this.chat, - // provider: this._modelSelection.provider, - // messages, - // tools: this._agentTools, - // maxOutputTokens: MAX_OUTPUT_TOKENS, - // contextWindow: this._modelConfig.contextWindow, - // onCompactionStarted: () => { - // this._streamWriter?.write({ - // type: 'data-compactionSummaryStarted', - // data: undefined, - // }); - // }, - // onCompactionFinished: (result) => { - // this._streamWriter?.write({ - // type: 'data-compaction', - // data: result, - // }); - // }, - // }); + await compactionService.compactConversationIfNeeded({ + chat: this.chat, + provider: this._modelSelection.provider, + messages, + tools: this._agentTools, + maxOutputTokens: MAX_OUTPUT_TOKENS, + contextWindow: this._modelConfig.contextWindow, + onCompactionStarted: () => { + this._streamWriter?.write({ + type: 'data-compactionSummaryStarted', + data: undefined, + }); + }, + onCompactionFinished: (result) => { + this._streamWriter?.write({ + type: 'data-compaction', + data: result, + }); + }, + }); return { messages: this._addCache(this._pruneMessages(messages)) }; } @@ -893,7 +893,6 @@ async function resolveImageUrls(messages: T[]): Promise): boolean { + if (typeof part.data === 'string' && part.data.startsWith('data:')) { + return true; + } + return typeof part.mediaType === 'string' && part.mediaType.startsWith('image/'); +} + export interface ITokenCounter { estimateMessages(messages: ModelMessage[]): number; estimateTools(tools: Record): Promise; @@ -32,7 +39,7 @@ export class TokenCounter implements ITokenCounter { let imageCount = 0; const sanitizedContent = (message.content as Record[]).map((part) => { - if (part.type === 'file' && typeof part.data === 'string' && part.data.startsWith('data:')) { + if (part.type === 'file' && typeof part.data === 'string' && isImagePart(part)) { imageCount++; return { ...part, data: '[image]' }; } diff --git a/apps/backend/tests/compaction.test.ts b/apps/backend/tests/compaction.test.ts index c473ac2db..d55580748 100644 --- a/apps/backend/tests/compaction.test.ts +++ b/apps/backend/tests/compaction.test.ts @@ -8,11 +8,13 @@ import type { AgentTools, UIMessage } from '../src/types/chat'; const mocks = vi.hoisted(() => ({ compactMock: vi.fn(), resolveProviderModelMock: vi.fn(), + resolveAnnotationModelIdMock: vi.fn(), scheduleSaveMock: vi.fn(), })); vi.mock('../src/utils/llm', () => ({ resolveProviderModel: mocks.resolveProviderModelMock, + resolveAnnotationModelId: mocks.resolveAnnotationModelIdMock, })); vi.mock('../src/utils/schedule-task', () => ({ @@ -43,6 +45,7 @@ describe('compactionService.compactConversationIfNeeded', () => { tokenCounter, }); mocks.resolveProviderModelMock.mockResolvedValue({ model: {} }); + mocks.resolveAnnotationModelIdMock.mockResolvedValue('gpt-4.1-mini'); mocks.compactMock.mockResolvedValue({ summary: 'Conversation summary', usage: { totalTokens: 123 }, diff --git a/apps/frontend/src/components/chat-messages/assistant-compaction.tsx b/apps/frontend/src/components/chat-messages/assistant-compaction.tsx index 481674a0b..27011434a 100644 --- a/apps/frontend/src/components/chat-messages/assistant-compaction.tsx +++ b/apps/frontend/src/components/chat-messages/assistant-compaction.tsx @@ -2,29 +2,28 @@ import { memo, useState } from 'react'; import { Streamdown } from 'streamdown'; import { Conversation, ConversationContent } from '../ui/conversation'; import { ErrorMessage } from '../ui/error-message'; +import { TextShimmer } from '../ui/text-shimmer'; import type { CompactionPart } from '@nao/backend/chat'; import { Expandable } from '@/components/ui/expandable'; export const AssistantCompaction = memo(({ part }: { part?: CompactionPart }) => { const [isExpanded, setIsExpanded] = useState(false); - const title = !part ? 'Compacting conversation' : part.error ? 'Compaction failed' : 'Compacted conversation'; + if (!part) { + return ; + } + + const title = part.error ? 'Compaction failed' : 'Compacted conversation'; return ( - +
- {part?.error ? ( + {part.error ? ( ) : ( - {part?.summary} + {part.summary} )}