This commit is contained in:
Bastian Wagner
2026-06-18 17:12:59 +02:00
parent 85a43d1b08
commit 63ee20bcd8
2 changed files with 196 additions and 3 deletions

View File

@@ -12,6 +12,7 @@ import {
UserListItem,
} from '../list-templates/list-template.types';
import { ListRealtimeService } from '../lists/list-realtime.service';
import { ListsService } from '../lists/lists.service';
import { AssistantChatLogEntity } from './assistant-chat-log.entity';
import {
AssistantAction,
@@ -93,6 +94,7 @@ export class AssistantService {
@InjectRepository(AssistantChatLogEntity)
private readonly chatLogsRepository: Repository<AssistantChatLogEntity>,
private readonly listRealtimeService: ListRealtimeService,
private readonly listsService: ListsService,
) {}
async chat(
@@ -101,6 +103,12 @@ export class AssistantService {
): Promise<AssistantChatResponse> {
const messages = this.normalizeMessages(request.messages);
const context = this.normalizeContext(request.context);
const localResponse = await this.tryHandleLocalListQuery(userId, messages);
if (localResponse) {
return localResponse;
}
const response = await this.callMistralAgent(userId, messages, context);
const content = this.extractAssistantContent(response);
const actions = this.extractActions(response);
@@ -122,6 +130,106 @@ export class AssistantService {
};
}
private async tryHandleLocalListQuery(
userId: string,
messages: AssistantChatMessage[],
): Promise<AssistantChatResponse | null> {
const latestUserMessage = [...messages]
.reverse()
.find((message) => message.role === 'user');
const content = latestUserMessage?.content.toLowerCase() ?? '';
if (!this.isListReadRequest(content)) {
return null;
}
const startedAt = Date.now();
const lists = await this.listsService.listLists(userId);
const wantsOpenLists = /\boffen\w*\b/.test(content);
const visibleLists = wantsOpenLists
? lists.filter((list) => !this.isCompletedList(list))
: lists;
const assistantContent = this.formatListsAnswer(visibleLists, wantsOpenLists);
await this.recordChatLog({
userId,
provider: 'listify',
endpoint: 'local:listLists',
agentId: null,
requestPayload: {
intent: wantsOpenLists ? 'list.open_lists' : 'list.list_lists',
latestUserMessage: latestUserMessage?.content,
},
responsePayload: { lists: visibleLists },
statusCode: 200,
durationMs: Date.now() - startedAt,
assistantContent,
errorMessage: null,
});
return {
message: {
role: 'assistant',
content: assistantContent,
},
actions: [],
};
}
private isListReadRequest(content: string): boolean {
const asksForLists = /\b(listen|liste)\b/.test(content);
const asksToRead =
/\b(zeig|zeige|anzeigen|abrufen|auflisten|welche|was|habe|gib|nenn)\w*\b/.test(
content,
);
const asksForOpenLists = /\boffen\w*\b/.test(content) && asksForLists;
const asksForOwnLists = /\bmeine listen\b/.test(content);
return asksToRead && (asksForOpenLists || asksForOwnLists);
}
private isCompletedList(list: UserList): boolean {
return (
list.items.length > 0 && list.items.every((item) => item.checked === true)
);
}
private formatListsAnswer(lists: UserList[], openOnly: boolean): string {
if (lists.length === 0) {
return openOnly
? 'Du hast aktuell keine offenen Listen.'
: 'Du hast aktuell keine Listen.';
}
const heading = openOnly
? `Du hast ${lists.length} offene ${lists.length === 1 ? 'Liste' : 'Listen'}:`
: `Du hast ${lists.length} ${lists.length === 1 ? 'Liste' : 'Listen'}:`;
const lines = [heading, ''];
for (const list of lists) {
const checkedCount = list.items.filter((item) => item.checked).length;
const openItems = list.items.filter((item) => !item.checked);
const ownerSuffix =
list.accessRole === 'collaborator'
? `, geteilt von ${list.ownerName || list.ownerEmail || 'Owner'}`
: '';
lines.push(
`- **${list.name}** (${checkedCount}/${list.items.length} erledigt${ownerSuffix})`,
);
for (const item of openItems.slice(0, 5)) {
lines.push(` - ${item.title}`);
}
if (openItems.length > 5) {
lines.push(` - ${openItems.length - 5} weitere offene Punkte`);
}
}
return lines.join('\n');
}
private async callMistralAgent(
userId: string,
messages: AssistantChatMessage[],
@@ -359,7 +467,9 @@ export class AssistantService {
private async recordChatLog(input: {
userId: string;
agentId: string;
provider?: string;
endpoint?: string;
agentId: string | null;
requestPayload: Record<string, unknown>;
responsePayload: unknown;
statusCode: number | null;
@@ -371,8 +481,8 @@ export class AssistantService {
this.chatLogsRepository.create({
id: randomUUID(),
userId: input.userId,
provider: 'mistral',
endpoint: this.endpoint,
provider: input.provider ?? 'mistral',
endpoint: input.endpoint ?? this.endpoint,
agentId: input.agentId,
statusCode: input.statusCode,
durationMs: input.durationMs,