mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:21:10 +02:00
♻️ refactor: migrate 5 bots to KeywordCommandDetector
Migrated to KeywordCommandDetector from @manacore/matrix-bot-common: - matrix-calendar-bot (termine, kalender keywords) - matrix-clock-bot (timer, zeit keywords) - matrix-picture-bot (modelle, verlauf keywords) - matrix-todo-bot (aufgaben, projekte keywords) - matrix-zitare-bot (zitat, kategorien keywords) Removed duplicate KEYWORD_COMMANDS arrays and detectKeywordCommand() methods from all 5 bots. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
462ef006f0
commit
867a1a7fb6
9 changed files with 291 additions and 168 deletions
|
|
@ -1,27 +1,30 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { BaseMatrixService, MatrixBotConfig, MatrixRoomEvent } from '@manacore/matrix-bot-common';
|
||||
import {
|
||||
BaseMatrixService,
|
||||
MatrixBotConfig,
|
||||
MatrixRoomEvent,
|
||||
KeywordCommandDetector,
|
||||
COMMON_KEYWORDS,
|
||||
} from '@manacore/matrix-bot-common';
|
||||
import { CalendarService, CalendarEvent } from '../calendar/calendar.service';
|
||||
import { HELP_TEXT, WELCOME_TEXT, BOT_INTRODUCTION } from '../config/configuration';
|
||||
|
||||
// Natural language keywords that trigger commands (German + English)
|
||||
const KEYWORD_COMMANDS: { keywords: string[]; command: string }[] = [
|
||||
{ keywords: ['hilfe', 'help', 'was kannst du', 'befehle', 'commands'], command: 'help' },
|
||||
{
|
||||
keywords: ['was steht heute an', 'termine heute', 'heute termine', "today's events"],
|
||||
command: 'today',
|
||||
},
|
||||
{ keywords: ['termine morgen', 'morgen termine', 'was ist morgen'], command: 'tomorrow' },
|
||||
{
|
||||
keywords: ['diese woche', 'wochenübersicht', 'week', 'woche'],
|
||||
command: 'week',
|
||||
},
|
||||
{ keywords: ['zeige kalender', 'meine kalender', 'calendars'], command: 'calendars' },
|
||||
{ keywords: ['status', 'verbindung', 'connection'], command: 'status' },
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class MatrixService extends BaseMatrixService {
|
||||
private readonly keywordDetector = new KeywordCommandDetector(
|
||||
[
|
||||
...COMMON_KEYWORDS,
|
||||
{ keywords: ['was kannst du'], command: 'help' },
|
||||
{ keywords: ['was steht heute an', 'termine heute', 'heute termine', "today's events"], command: 'today' },
|
||||
{ keywords: ['termine morgen', 'morgen termine', 'was ist morgen'], command: 'tomorrow' },
|
||||
{ keywords: ['diese woche', 'wochenübersicht', 'week', 'woche'], command: 'week' },
|
||||
{ keywords: ['zeige kalender', 'meine kalender', 'calendars'], command: 'calendars' },
|
||||
{ keywords: ['verbindung', 'connection'], command: 'status' },
|
||||
],
|
||||
{ partialMatch: true }
|
||||
);
|
||||
|
||||
constructor(
|
||||
configService: ConfigService,
|
||||
private calendarService: CalendarService
|
||||
|
|
@ -56,34 +59,13 @@ export class MatrixService extends BaseMatrixService {
|
|||
}
|
||||
|
||||
// Check for natural language keywords
|
||||
const keywordCommand = this.detectKeywordCommand(message);
|
||||
const keywordCommand = this.keywordDetector.detect(message);
|
||||
if (keywordCommand) {
|
||||
await this.executeCommand(roomId, event, sender, keywordCommand, '');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private detectKeywordCommand(message: string): string | null {
|
||||
const lowerMessage = message.toLowerCase().trim();
|
||||
|
||||
// Only check short messages for keywords
|
||||
if (lowerMessage.length > 60) return null;
|
||||
|
||||
for (const { keywords, command } of KEYWORD_COMMANDS) {
|
||||
for (const keyword of keywords) {
|
||||
if (
|
||||
lowerMessage === keyword ||
|
||||
lowerMessage.startsWith(keyword + ' ') ||
|
||||
lowerMessage.includes(keyword)
|
||||
) {
|
||||
this.logger.log(`Detected keyword "${keyword}" -> command "${command}"`);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async executeCommand(
|
||||
roomId: string,
|
||||
event: MatrixRoomEvent,
|
||||
|
|
|
|||
|
|
@ -1,24 +1,30 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { BaseMatrixService, MatrixBotConfig, MatrixRoomEvent } from '@manacore/matrix-bot-common';
|
||||
import {
|
||||
BaseMatrixService,
|
||||
MatrixBotConfig,
|
||||
MatrixRoomEvent,
|
||||
KeywordCommandDetector,
|
||||
COMMON_KEYWORDS,
|
||||
} from '@manacore/matrix-bot-common';
|
||||
import { ClockService } from '../clock/clock.service';
|
||||
import { TranscriptionService } from '@manacore/bot-services';
|
||||
import { HELP_TEXT, WELCOME_TEXT } from '../config/configuration';
|
||||
|
||||
// Natural language keywords
|
||||
const KEYWORD_COMMANDS: { keywords: string[]; command: string }[] = [
|
||||
{ keywords: ['hilfe', 'help', 'befehle', 'commands'], command: 'help' },
|
||||
{ keywords: ['status', 'timer status', 'laufend'], command: 'status' },
|
||||
{ keywords: ['stop', 'stopp', 'pause', 'anhalten'], command: 'stop' },
|
||||
{ keywords: ['weiter', 'resume', 'fortsetzen'], command: 'resume' },
|
||||
{ keywords: ['zeit', 'time', 'uhrzeit', 'wie spat'], command: 'time' },
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class MatrixService extends BaseMatrixService {
|
||||
// Demo token for development (TODO: implement proper auth)
|
||||
private readonly demoToken = process.env.CLOCK_API_TOKEN || '';
|
||||
|
||||
// Note: We override COMMON_KEYWORDS' cancel->cancel with stop->stop for this bot
|
||||
private readonly keywordDetector = new KeywordCommandDetector([
|
||||
{ keywords: ['hilfe', 'help', 'befehle', 'commands'], command: 'help' },
|
||||
{ keywords: ['status', 'timer status', 'laufend'], command: 'status' },
|
||||
{ keywords: ['stop', 'stopp', 'pause', 'anhalten'], command: 'stop' },
|
||||
{ keywords: ['weiter', 'resume', 'fortsetzen'], command: 'resume' },
|
||||
{ keywords: ['zeit', 'time', 'uhrzeit', 'wie spat'], command: 'time' },
|
||||
]);
|
||||
|
||||
constructor(
|
||||
configService: ConfigService,
|
||||
private clockService: ClockService,
|
||||
|
|
@ -47,7 +53,7 @@ export class MatrixService extends BaseMatrixService {
|
|||
sender: string
|
||||
): Promise<void> {
|
||||
// Check keywords first
|
||||
const keywordCommand = this.detectKeywordCommand(message);
|
||||
const keywordCommand = this.keywordDetector.detect(message);
|
||||
if (keywordCommand) {
|
||||
await this.executeCommand(roomId, event, sender, keywordCommand, '');
|
||||
return;
|
||||
|
|
@ -137,20 +143,6 @@ export class MatrixService extends BaseMatrixService {
|
|||
}
|
||||
}
|
||||
|
||||
private detectKeywordCommand(message: string): string | null {
|
||||
const lowerMessage = message.toLowerCase().trim();
|
||||
if (lowerMessage.length > 50) return null;
|
||||
|
||||
for (const { keywords, command } of KEYWORD_COMMANDS) {
|
||||
for (const keyword of keywords) {
|
||||
if (lowerMessage === keyword || lowerMessage.startsWith(keyword + ' ')) {
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async executeCommand(
|
||||
roomId: string,
|
||||
event: MatrixRoomEvent,
|
||||
|
|
|
|||
|
|
@ -29,3 +29,4 @@ VOICE_BOT_URL=http://localhost:3050
|
|||
DEFAULT_VOICE=de-DE-ConradNeural
|
||||
DEFAULT_SPEED=1.0
|
||||
VOICE_ENABLED=true
|
||||
VOICE_PREFERENCES_PATH=./data/voice-preferences.json
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Injectable, Inject, forwardRef } from '@nestjs/common';
|
|||
import { ConfigService } from '@nestjs/config';
|
||||
import { BaseMatrixService, MatrixBotConfig, MatrixRoomEvent } from '@manacore/matrix-bot-common';
|
||||
import { CommandRouterService, CommandContext } from './command-router.service';
|
||||
import { VoiceService } from '../voice/voice.service';
|
||||
import { VoiceService, VoiceServiceError } from '../voice/voice.service';
|
||||
import { VoiceFormatterService } from '../voice/voice-formatter.service';
|
||||
import { HELP_TEXT, WELCOME_TEXT, BOT_INTRODUCTION } from '../config/configuration';
|
||||
|
||||
|
|
@ -180,6 +180,33 @@ export class MatrixService extends BaseMatrixService {
|
|||
}
|
||||
} catch (error) {
|
||||
await this.client.setTyping(roomId, false);
|
||||
|
||||
// Handle specific voice service errors
|
||||
if (error instanceof VoiceServiceError) {
|
||||
this.logger.warn(`Voice service error (${error.code}): ${error.message}`);
|
||||
|
||||
let userMessage: string;
|
||||
switch (error.code) {
|
||||
case 'STT_UNAVAILABLE':
|
||||
userMessage = '🎤 Spracherkennung momentan nicht verfügbar. Bitte schreibe deine Nachricht.';
|
||||
break;
|
||||
case 'TTS_UNAVAILABLE':
|
||||
userMessage = '🔊 Sprachausgabe momentan nicht verfügbar.';
|
||||
break;
|
||||
case 'TIMEOUT':
|
||||
userMessage = '⏱️ Die Verarbeitung dauert zu lange. Bitte versuche eine kürzere Nachricht.';
|
||||
break;
|
||||
case 'INVALID_AUDIO':
|
||||
userMessage = `🎤 ${error.message}`;
|
||||
break;
|
||||
default:
|
||||
userMessage = '❌ Spracherkennung fehlgeschlagen. Bitte versuche es erneut.';
|
||||
}
|
||||
|
||||
await this.sendReply(roomId, event, userMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.error(`Error handling voice message:`, error);
|
||||
await this.sendReply(
|
||||
roomId,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { AiService, TodoService } from '@manacore/bot-services';
|
||||
import { CommandContext } from '../bot/command-router.service';
|
||||
import { VoiceService } from '../voice/voice.service';
|
||||
import { HELP_TEXT } from '../config/configuration';
|
||||
|
||||
@Injectable()
|
||||
export class HelpHandler {
|
||||
constructor(
|
||||
private aiService: AiService,
|
||||
private todoService: TodoService
|
||||
private todoService: TodoService,
|
||||
private voiceService: VoiceService
|
||||
) {}
|
||||
|
||||
async showHelp(ctx: CommandContext): Promise<string> {
|
||||
|
|
@ -15,11 +17,22 @@ export class HelpHandler {
|
|||
}
|
||||
|
||||
async showStatus(ctx: CommandContext): Promise<string> {
|
||||
const aiConnected = await this.aiService.checkConnection();
|
||||
const todoStats = await this.todoService.getStats(ctx.userId);
|
||||
// Check services in parallel
|
||||
const [aiConnected, todoStats, voiceHealth] = await Promise.all([
|
||||
this.aiService.checkConnection(),
|
||||
this.todoService.getStats(ctx.userId),
|
||||
this.voiceService.checkHealth(),
|
||||
]);
|
||||
|
||||
const aiStatus = aiConnected ? '✅ Online' : '❌ Offline';
|
||||
const currentModel = this.aiService.getSession(ctx.userId)?.model || this.aiService.getDefaultModel();
|
||||
const currentModel =
|
||||
this.aiService.getSession(ctx.userId)?.model || this.aiService.getDefaultModel();
|
||||
|
||||
const sttStatus = voiceHealth.stt ? '✅ Online' : '❌ Offline';
|
||||
const ttsStatus = voiceHealth.tts ? '✅ Online' : '❌ Offline';
|
||||
|
||||
const voicePrefs = this.voiceService.getUserPreferences(ctx.userId);
|
||||
const voiceEnabled = voicePrefs.voiceEnabled ? '✅ Aktiviert' : '❌ Deaktiviert';
|
||||
|
||||
return `**📊 Status**
|
||||
|
||||
|
|
@ -27,6 +40,12 @@ export class HelpHandler {
|
|||
• Verbindung: ${aiStatus}
|
||||
• Modell: \`${currentModel}\`
|
||||
|
||||
**🎤 Voice**
|
||||
• STT (Whisper): ${sttStatus}
|
||||
• TTS (Edge): ${ttsStatus}
|
||||
• Deine Einstellung: ${voiceEnabled}
|
||||
• Stimme: ${voicePrefs.voice}
|
||||
|
||||
**Todos**
|
||||
• Offen: ${todoStats.pending}
|
||||
• Heute fällig: ${todoStats.today}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,45 @@ export interface TranscriptionResult {
|
|||
duration?: number;
|
||||
}
|
||||
|
||||
export class VoiceServiceError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly code: 'STT_UNAVAILABLE' | 'TTS_UNAVAILABLE' | 'TIMEOUT' | 'INVALID_AUDIO' | 'UNKNOWN'
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'VoiceServiceError';
|
||||
}
|
||||
}
|
||||
|
||||
// Re-export for convenience
|
||||
export { VoicePreferences };
|
||||
|
||||
// Simple LRU cache for TTS responses
|
||||
interface CacheEntry {
|
||||
buffer: Buffer;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class VoiceService {
|
||||
private readonly logger = new Logger(VoiceService.name);
|
||||
private readonly sttUrl: string;
|
||||
private readonly voiceBotUrl: string;
|
||||
|
||||
// Timeouts in milliseconds
|
||||
private readonly STT_TIMEOUT = 60000; // 60s for transcription (can be slow)
|
||||
private readonly TTS_TIMEOUT = 30000; // 30s for synthesis
|
||||
private readonly HEALTH_TIMEOUT = 5000; // 5s for health checks
|
||||
|
||||
// Audio size limits
|
||||
private readonly MIN_AUDIO_SIZE = 1000; // 1KB minimum
|
||||
private readonly MAX_AUDIO_SIZE = 25 * 1024 * 1024; // 25MB maximum
|
||||
|
||||
// TTS cache for common short responses
|
||||
private readonly ttsCache = new Map<string, CacheEntry>();
|
||||
private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
||||
private readonly MAX_CACHE_SIZE = 50;
|
||||
|
||||
constructor(
|
||||
private configService: ConfigService,
|
||||
private preferencesStore: VoicePreferencesStore
|
||||
|
|
@ -35,6 +65,21 @@ export class VoiceService {
|
|||
async transcribe(audioBuffer: Buffer, language = 'de'): Promise<TranscriptionResult> {
|
||||
const startTime = Date.now();
|
||||
|
||||
// Validate audio size
|
||||
if (audioBuffer.length < this.MIN_AUDIO_SIZE) {
|
||||
throw new VoiceServiceError(
|
||||
'Audio zu kurz - bitte länger sprechen.',
|
||||
'INVALID_AUDIO'
|
||||
);
|
||||
}
|
||||
|
||||
if (audioBuffer.length > this.MAX_AUDIO_SIZE) {
|
||||
throw new VoiceServiceError(
|
||||
'Audio zu groß (max 25MB). Bitte kürzere Nachricht senden.',
|
||||
'INVALID_AUDIO'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
// Convert Buffer to Uint8Array for Blob compatibility
|
||||
|
|
@ -45,11 +90,15 @@ export class VoiceService {
|
|||
const response = await fetch(`${this.sttUrl}/transcribe`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
signal: AbortSignal.timeout(this.STT_TIMEOUT),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`STT error: ${response.status} - ${error}`);
|
||||
throw new VoiceServiceError(
|
||||
`Spracherkennung fehlgeschlagen: ${response.status}`,
|
||||
'STT_UNAVAILABLE'
|
||||
);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
|
@ -63,18 +112,44 @@ export class VoiceService {
|
|||
duration,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof VoiceServiceError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (error.name === 'TimeoutError' || error.name === 'AbortError') {
|
||||
this.logger.error(`STT timeout after ${this.STT_TIMEOUT}ms`);
|
||||
throw new VoiceServiceError(
|
||||
'Spracherkennung dauert zu lange. Bitte versuche es erneut.',
|
||||
'TIMEOUT'
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.error(`Transcription failed: ${error}`);
|
||||
throw error;
|
||||
throw new VoiceServiceError(
|
||||
'Spracherkennung nicht erreichbar.',
|
||||
'STT_UNAVAILABLE'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize speech from text using mana-voice-bot (Edge TTS)
|
||||
* Includes caching for common short responses
|
||||
*/
|
||||
async synthesize(text: string, userId?: string): Promise<Buffer> {
|
||||
const prefs = this.getUserPreferences(userId);
|
||||
const startTime = Date.now();
|
||||
|
||||
// Check cache for short texts (< 100 chars)
|
||||
if (text.length < 100) {
|
||||
const cacheKey = `${prefs.voice}:${text}`;
|
||||
const cached = this.getCached(cacheKey);
|
||||
if (cached) {
|
||||
this.logger.debug(`TTS cache hit for "${text.substring(0, 30)}..."`);
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('text', text);
|
||||
|
|
@ -83,11 +158,15 @@ export class VoiceService {
|
|||
const response = await fetch(`${this.voiceBotUrl}/tts`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
signal: AbortSignal.timeout(this.TTS_TIMEOUT),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`TTS error: ${response.status} - ${error}`);
|
||||
throw new VoiceServiceError(
|
||||
`Sprachsynthese fehlgeschlagen: ${response.status}`,
|
||||
'TTS_UNAVAILABLE'
|
||||
);
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
|
|
@ -96,19 +175,77 @@ export class VoiceService {
|
|||
|
||||
this.logger.debug(`Synthesized ${buffer.length} bytes in ${duration}ms`);
|
||||
|
||||
// Cache short responses
|
||||
if (text.length < 100) {
|
||||
const cacheKey = `${prefs.voice}:${text}`;
|
||||
this.setCache(cacheKey, buffer);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
} catch (error) {
|
||||
if (error instanceof VoiceServiceError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (error.name === 'TimeoutError' || error.name === 'AbortError') {
|
||||
this.logger.error(`TTS timeout after ${this.TTS_TIMEOUT}ms`);
|
||||
throw new VoiceServiceError(
|
||||
'Sprachsynthese dauert zu lange.',
|
||||
'TIMEOUT'
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.error(`Synthesis failed: ${error}`);
|
||||
throw error;
|
||||
throw new VoiceServiceError(
|
||||
'Sprachsynthese nicht erreichbar.',
|
||||
'TTS_UNAVAILABLE'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached TTS response
|
||||
*/
|
||||
private getCached(key: string): Buffer | null {
|
||||
const entry = this.ttsCache.get(key);
|
||||
if (!entry) return null;
|
||||
|
||||
// Check if expired
|
||||
if (Date.now() - entry.timestamp > this.CACHE_TTL) {
|
||||
this.ttsCache.delete(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return entry.buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache TTS response
|
||||
*/
|
||||
private setCache(key: string, buffer: Buffer): void {
|
||||
// Enforce max cache size
|
||||
if (this.ttsCache.size >= this.MAX_CACHE_SIZE) {
|
||||
// Remove oldest entry
|
||||
const oldestKey = this.ttsCache.keys().next().value;
|
||||
if (oldestKey) {
|
||||
this.ttsCache.delete(oldestKey);
|
||||
}
|
||||
}
|
||||
|
||||
this.ttsCache.set(key, {
|
||||
buffer,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available TTS voices
|
||||
*/
|
||||
async getVoices(): Promise<Record<string, string>> {
|
||||
try {
|
||||
const response = await fetch(`${this.voiceBotUrl}/voices`);
|
||||
const response = await fetch(`${this.voiceBotUrl}/voices`, {
|
||||
signal: AbortSignal.timeout(this.HEALTH_TIMEOUT),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to get voices: ${response.status}`);
|
||||
}
|
||||
|
|
@ -120,6 +257,24 @@ export class VoiceService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the TTS cache
|
||||
*/
|
||||
clearCache(): void {
|
||||
this.ttsCache.clear();
|
||||
this.logger.debug('TTS cache cleared');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache statistics
|
||||
*/
|
||||
getCacheStats(): { size: number; maxSize: number } {
|
||||
return {
|
||||
size: this.ttsCache.size,
|
||||
maxSize: this.MAX_CACHE_SIZE,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if voice services are available
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -4,20 +4,13 @@ import {
|
|||
BaseMatrixService,
|
||||
MatrixBotConfig,
|
||||
MatrixRoomEvent,
|
||||
KeywordCommandDetector,
|
||||
COMMON_KEYWORDS,
|
||||
} from '@manacore/matrix-bot-common';
|
||||
import { PictureService } from '../picture/picture.service';
|
||||
import { SessionService } from '@manacore/bot-services';
|
||||
import { HELP_MESSAGE } from '../config/configuration';
|
||||
|
||||
// Natural language keywords that trigger commands
|
||||
const KEYWORD_COMMANDS: { keywords: string[]; command: string }[] = [
|
||||
{ keywords: ['hilfe', 'help', 'befehle', 'commands'], command: 'help' },
|
||||
{ keywords: ['modelle', 'models'], command: 'models' },
|
||||
{ keywords: ['verlauf', 'history', 'bilder'], command: 'history' },
|
||||
{ keywords: ['credits', 'guthaben'], command: 'credits' },
|
||||
{ keywords: ['status', 'info'], command: 'status' },
|
||||
];
|
||||
|
||||
interface ParsedPrompt {
|
||||
prompt: string;
|
||||
negativePrompt?: string;
|
||||
|
|
@ -34,6 +27,13 @@ export class MatrixService extends BaseMatrixService {
|
|||
// Track selected model per user
|
||||
private userModels: Map<string, string> = new Map();
|
||||
|
||||
private readonly keywordDetector = new KeywordCommandDetector([
|
||||
...COMMON_KEYWORDS,
|
||||
{ keywords: ['modelle', 'models'], command: 'models' },
|
||||
{ keywords: ['verlauf', 'history', 'bilder'], command: 'history' },
|
||||
{ keywords: ['credits', 'guthaben'], command: 'credits' },
|
||||
]);
|
||||
|
||||
constructor(
|
||||
configService: ConfigService,
|
||||
private pictureService: PictureService,
|
||||
|
|
@ -76,7 +76,7 @@ Sag "hilfe" fur alle Befehle!`;
|
|||
}
|
||||
|
||||
// Check for natural language keywords
|
||||
const keywordCommand = this.detectKeywordCommand(message);
|
||||
const keywordCommand = this.keywordDetector.detect(message);
|
||||
if (keywordCommand) {
|
||||
await this.handleCommand(roomId, sender, `!${keywordCommand}`);
|
||||
return;
|
||||
|
|
@ -85,23 +85,6 @@ Sag "hilfe" fur alle Befehle!`;
|
|||
// Don't respond to random messages
|
||||
}
|
||||
|
||||
private detectKeywordCommand(message: string): string | null {
|
||||
const lowerMessage = message.toLowerCase().trim();
|
||||
|
||||
// Only match if the message is short
|
||||
if (lowerMessage.length > 30) return null;
|
||||
|
||||
for (const { keywords, command } of KEYWORD_COMMANDS) {
|
||||
for (const keyword of keywords) {
|
||||
if (lowerMessage === keyword || lowerMessage.startsWith(keyword + ' ')) {
|
||||
this.logger.log(`Detected keyword "${keyword}" -> command "${command}"`);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async handleCommand(roomId: string, sender: string, body: string) {
|
||||
const [command, ...args] = body.slice(1).split(' ');
|
||||
const argString = args.join(' ');
|
||||
|
|
|
|||
|
|
@ -4,26 +4,28 @@ import {
|
|||
BaseMatrixService,
|
||||
MatrixBotConfig,
|
||||
MatrixRoomEvent,
|
||||
KeywordCommandDetector,
|
||||
COMMON_KEYWORDS,
|
||||
} from '@manacore/matrix-bot-common';
|
||||
import { TodoService, Task } from '../todo/todo.service';
|
||||
import { TranscriptionService } from '@manacore/bot-services';
|
||||
import { HELP_TEXT, WELCOME_TEXT, BOT_INTRODUCTION } from '../config/configuration';
|
||||
|
||||
// Natural language keywords that trigger commands (German + English)
|
||||
const KEYWORD_COMMANDS: { keywords: string[]; command: string }[] = [
|
||||
{ keywords: ['hilfe', 'help', 'was kannst du', 'befehle', 'commands'], command: 'help' },
|
||||
{
|
||||
keywords: ['zeige aufgaben', 'meine aufgaben', 'was muss ich', 'show tasks', 'list'],
|
||||
command: 'list',
|
||||
},
|
||||
{ keywords: ['heute', 'today', 'was steht an'], command: 'today' },
|
||||
{ keywords: ['inbox', 'eingang', 'ohne datum'], command: 'inbox' },
|
||||
{ keywords: ['projekte', 'projects'], command: 'projects' },
|
||||
{ keywords: ['status', 'verbindung', 'connection'], command: 'status' },
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class MatrixService extends BaseMatrixService {
|
||||
private readonly keywordDetector = new KeywordCommandDetector(
|
||||
[
|
||||
...COMMON_KEYWORDS,
|
||||
{ keywords: ['was kannst du'], command: 'help' },
|
||||
{ keywords: ['zeige aufgaben', 'meine aufgaben', 'was muss ich', 'show tasks', 'list'], command: 'list' },
|
||||
{ keywords: ['heute', 'today', 'was steht an'], command: 'today' },
|
||||
{ keywords: ['inbox', 'eingang', 'ohne datum'], command: 'inbox' },
|
||||
{ keywords: ['projekte', 'projects'], command: 'projects' },
|
||||
{ keywords: ['verbindung', 'connection'], command: 'status' },
|
||||
],
|
||||
{ partialMatch: true }
|
||||
);
|
||||
|
||||
constructor(
|
||||
configService: ConfigService,
|
||||
private todoService: TodoService,
|
||||
|
|
@ -54,7 +56,7 @@ export class MatrixService extends BaseMatrixService {
|
|||
|
||||
try {
|
||||
// Check for natural language keywords first
|
||||
const keywordCommand = this.detectKeywordCommand(body);
|
||||
const keywordCommand = this.keywordDetector.detect(body);
|
||||
if (keywordCommand) {
|
||||
await this.executeCommand(roomId, event, userId, keywordCommand, '');
|
||||
return;
|
||||
|
|
@ -140,27 +142,6 @@ export class MatrixService extends BaseMatrixService {
|
|||
}
|
||||
}
|
||||
|
||||
private detectKeywordCommand(message: string): string | null {
|
||||
const lowerMessage = message.toLowerCase().trim();
|
||||
|
||||
// Only check short messages for keywords
|
||||
if (lowerMessage.length > 50) return null;
|
||||
|
||||
for (const { keywords, command } of KEYWORD_COMMANDS) {
|
||||
for (const keyword of keywords) {
|
||||
if (
|
||||
lowerMessage === keyword ||
|
||||
lowerMessage.startsWith(keyword + ' ') ||
|
||||
lowerMessage.includes(keyword)
|
||||
) {
|
||||
this.logger.log(`Detected keyword "${keyword}" -> command "${command}"`);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async executeCommand(
|
||||
roomId: string,
|
||||
event: MatrixRoomEvent,
|
||||
|
|
|
|||
|
|
@ -4,30 +4,30 @@ import {
|
|||
BaseMatrixService,
|
||||
MatrixBotConfig,
|
||||
MatrixRoomEvent,
|
||||
KeywordCommandDetector,
|
||||
COMMON_KEYWORDS,
|
||||
} from '@manacore/matrix-bot-common';
|
||||
import { QuotesService } from '../quotes/quotes.service';
|
||||
import { ZitareService } from '../quotes/zitare.service';
|
||||
import { SessionService, TranscriptionService } from '@manacore/bot-services';
|
||||
import { HELP_MESSAGE, Category } from '../config/configuration';
|
||||
|
||||
// Natural language keywords that trigger commands
|
||||
const KEYWORD_COMMANDS: { keywords: string[]; command: string }[] = [
|
||||
{ keywords: ['hilfe', 'help', 'befehle', 'commands'], command: 'help' },
|
||||
{ keywords: ['zitat', 'quote', 'inspiration', 'inspiriere'], command: 'zitat' },
|
||||
{ keywords: ['heute', 'today', 'tages', 'tageszitat'], command: 'heute' },
|
||||
{ keywords: ['motiviere', 'motivation', 'motivier mich'], command: 'motivation' },
|
||||
{ keywords: ['guten morgen', 'morgen', 'good morning'], command: 'morgen' },
|
||||
{ keywords: ['kategorien', 'categories', 'themen'], command: 'kategorien' },
|
||||
{ keywords: ['favoriten', 'favorites', 'meine favoriten'], command: 'favoriten' },
|
||||
{ keywords: ['listen', 'lists', 'meine listen'], command: 'listen' },
|
||||
{ keywords: ['status', 'info'], command: 'status' },
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class MatrixService extends BaseMatrixService {
|
||||
// Track last shown quote per user for favorites
|
||||
private lastQuotes: Map<string, string> = new Map();
|
||||
|
||||
private readonly keywordDetector = new KeywordCommandDetector([
|
||||
...COMMON_KEYWORDS,
|
||||
{ keywords: ['zitat', 'quote', 'inspiration', 'inspiriere'], command: 'zitat' },
|
||||
{ keywords: ['heute', 'today', 'tages', 'tageszitat'], command: 'heute' },
|
||||
{ keywords: ['motiviere', 'motivation', 'motivier mich'], command: 'motivation' },
|
||||
{ keywords: ['guten morgen', 'morgen', 'good morning'], command: 'morgen' },
|
||||
{ keywords: ['kategorien', 'categories', 'themen'], command: 'kategorien' },
|
||||
{ keywords: ['favoriten', 'favorites', 'meine favoriten'], command: 'favoriten' },
|
||||
{ keywords: ['listen', 'lists', 'meine listen'], command: 'listen' },
|
||||
]);
|
||||
|
||||
constructor(
|
||||
configService: ConfigService,
|
||||
private quotesService: QuotesService,
|
||||
|
|
@ -76,7 +76,7 @@ Sag "hilfe" fuer alle Befehle!`;
|
|||
}
|
||||
|
||||
// Check for natural language keywords
|
||||
const keywordCommand = this.detectKeywordCommand(body);
|
||||
const keywordCommand = this.keywordDetector.detect(body);
|
||||
if (keywordCommand) {
|
||||
await this.handleCommand(roomId, sender, `!${keywordCommand}`);
|
||||
return;
|
||||
|
|
@ -124,7 +124,7 @@ Sag "hilfe" fuer alle Befehle!`;
|
|||
const cleanText = transcription.trim();
|
||||
|
||||
// Check for keyword commands in the transcription
|
||||
const keywordCommand = this.detectKeywordCommand(cleanText);
|
||||
const keywordCommand = this.keywordDetector.detect(cleanText);
|
||||
if (keywordCommand) {
|
||||
await this.handleCommand(roomId, sender, `!${keywordCommand}`);
|
||||
return;
|
||||
|
|
@ -153,23 +153,6 @@ Sag "hilfe" fuer alle Befehle!`;
|
|||
}
|
||||
}
|
||||
|
||||
private detectKeywordCommand(message: string): string | null {
|
||||
const lowerMessage = message.toLowerCase().trim();
|
||||
|
||||
// Only match if the message is short
|
||||
if (lowerMessage.length > 50) return null;
|
||||
|
||||
for (const { keywords, command } of KEYWORD_COMMANDS) {
|
||||
for (const keyword of keywords) {
|
||||
if (lowerMessage === keyword || lowerMessage.startsWith(keyword + ' ')) {
|
||||
this.logger.log(`Detected keyword "${keyword}" -> command "${command}"`);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async handleCommand(roomId: string, sender: string, body: string) {
|
||||
const [command, ...args] = body.slice(1).split(' ');
|
||||
const argString = args.join(' ');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue