mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 02:46:42 +02:00
feat(mana-media): add centralized media storage with NutriPhi integration
- Implement mana-media service with PostgreSQL/Drizzle ORM persistence - Add content-addressable storage (SHA-256) for automatic deduplication - Add Matrix MXC URL import endpoint to copy images from Matrix - Create @manacore/media-client package for service consumption - Integrate mana-media into NutriPhi bot for persistent image storage - Update pnpm-workspace.yaml to include nested service packages - Add mana-media to docker-compose with port 3015 Images sent to NutriPhi bot are now stored in mana-media after analysis, providing persistent storage with deduplication across all apps. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
171cf7a854
commit
d4663b5643
31 changed files with 2114 additions and 4419 deletions
|
|
@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
|
|||
import { MatrixService } from './matrix.service';
|
||||
import { NutriPhiModule } from '../nutriphi/nutriphi.module';
|
||||
import { SessionModule, TranscriptionModule, CreditModule } from '@manacore/bot-services';
|
||||
import { MediaModule } from '../media/media.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -9,6 +10,7 @@ import { SessionModule, TranscriptionModule, CreditModule } from '@manacore/bot-
|
|||
SessionModule.forRoot({ storageMode: 'redis' }),
|
||||
TranscriptionModule.forRoot(),
|
||||
CreditModule.forRoot(),
|
||||
MediaModule,
|
||||
],
|
||||
providers: [MatrixService],
|
||||
exports: [MatrixService],
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
WeeklyStats,
|
||||
} from '../nutriphi/nutriphi.service';
|
||||
import { SessionService, TranscriptionService, CreditService } from '@manacore/bot-services';
|
||||
import { MediaService } from '../media/media.service';
|
||||
import { HELP_MESSAGE, MEAL_TYPE_LABELS } from '../config/configuration';
|
||||
|
||||
const PHOTO_ANALYSIS_CREDITS = 3;
|
||||
|
|
@ -36,7 +37,8 @@ export class MatrixService extends BaseMatrixService {
|
|||
private nutriphiService: NutriPhiService,
|
||||
private sessionService: SessionService,
|
||||
private transcriptionService: TranscriptionService,
|
||||
private creditService: CreditService
|
||||
private creditService: CreditService,
|
||||
private mediaService: MediaService
|
||||
) {
|
||||
super(configService);
|
||||
}
|
||||
|
|
@ -114,6 +116,19 @@ Sag "hilfe" fur alle Befehle!`;
|
|||
|
||||
const response = this.formatAnalysisResult(result);
|
||||
await this.sendMessage(roomId, response);
|
||||
|
||||
// Store image in mana-media for persistent storage (non-blocking)
|
||||
// Use Matrix sender ID as user identifier
|
||||
this.mediaService
|
||||
.storeFromMatrix(mxcUrl, sender)
|
||||
.then((mediaResult) => {
|
||||
if (mediaResult) {
|
||||
this.logger.log(`Image stored in mana-media: ${mediaResult.id}`);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.logger.warn(`Failed to store image in mana-media: ${error}`);
|
||||
});
|
||||
} catch (error) {
|
||||
await this.client.setTyping(roomId, false);
|
||||
const errorMsg = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ export default () => ({
|
|||
stt: {
|
||||
url: process.env.STT_URL || 'http://localhost:3020',
|
||||
},
|
||||
media: {
|
||||
url: process.env.MANA_MEDIA_URL || 'http://localhost:3015',
|
||||
},
|
||||
});
|
||||
|
||||
export const HELP_MESSAGE = `**NutriPhi Bot - KI-Ernahrungsassistent**
|
||||
|
|
|
|||
8
services/matrix-nutriphi-bot/src/media/media.module.ts
Normal file
8
services/matrix-nutriphi-bot/src/media/media.module.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { MediaService } from './media.service';
|
||||
|
||||
@Module({
|
||||
providers: [MediaService],
|
||||
exports: [MediaService],
|
||||
})
|
||||
export class MediaModule {}
|
||||
83
services/matrix-nutriphi-bot/src/media/media.service.ts
Normal file
83
services/matrix-nutriphi-bot/src/media/media.service.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { MediaClient, MediaResult } from '@manacore/media-client';
|
||||
|
||||
@Injectable()
|
||||
export class MediaService implements OnModuleInit {
|
||||
private readonly logger = new Logger(MediaService.name);
|
||||
private client: MediaClient | null = null;
|
||||
|
||||
constructor(private configService: ConfigService) {}
|
||||
|
||||
onModuleInit() {
|
||||
const mediaUrl = this.configService.get<string>('media.url');
|
||||
if (mediaUrl) {
|
||||
this.client = new MediaClient(mediaUrl);
|
||||
this.logger.log(`MediaClient initialized with URL: ${mediaUrl}`);
|
||||
} else {
|
||||
this.logger.warn('MANA_MEDIA_URL not configured, media storage disabled');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an image from a Matrix MXC URL in mana-media.
|
||||
* Returns the media record if successful, null if disabled or failed.
|
||||
*/
|
||||
async storeFromMatrix(mxcUrl: string, userId: string): Promise<MediaResult | null> {
|
||||
if (!this.client) {
|
||||
this.logger.debug('Media storage disabled, skipping storage');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.client.importFromMatrix({
|
||||
mxcUrl,
|
||||
app: 'nutriphi',
|
||||
userId,
|
||||
});
|
||||
|
||||
this.logger.log(`Stored media from Matrix: ${result.id} (hash: ${result.hash})`);
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to store media from Matrix: ${error}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file already exists by hash
|
||||
*/
|
||||
async checkExists(hash: string): Promise<MediaResult | null> {
|
||||
if (!this.client) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.client.getByHash(hash);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get media by ID
|
||||
*/
|
||||
async get(id: string): Promise<MediaResult | null> {
|
||||
if (!this.client) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.client.get(id);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if media service is available
|
||||
*/
|
||||
isEnabled(): boolean {
|
||||
return this.client !== null;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue