Some checks failed
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
128 lines
4.3 KiB
TypeScript
128 lines
4.3 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
Get,
|
|
HttpCode,
|
|
HttpStatus,
|
|
Inject,
|
|
NotFoundException,
|
|
Param,
|
|
Post,
|
|
Query,
|
|
UseGuards,
|
|
} from '@nestjs/common';
|
|
import type { Memory } from '@mosaic/memory';
|
|
import { MEMORY } from './memory.tokens.js';
|
|
import { AuthGuard } from '../auth/auth.guard.js';
|
|
import { CurrentUser } from '../auth/current-user.decorator.js';
|
|
import { EmbeddingService } from './embedding.service.js';
|
|
import type { UpsertPreferenceDto, CreateInsightDto, SearchMemoryDto } from './memory.dto.js';
|
|
|
|
@Controller('api/memory')
|
|
@UseGuards(AuthGuard)
|
|
export class MemoryController {
|
|
constructor(
|
|
@Inject(MEMORY) private readonly memory: Memory,
|
|
@Inject(EmbeddingService) private readonly embeddings: EmbeddingService,
|
|
) {}
|
|
|
|
// ─── Preferences ────────────────────────────────────────────────────
|
|
|
|
@Get('preferences')
|
|
async listPreferences(@CurrentUser() user: { id: string }, @Query('category') category?: string) {
|
|
if (category) {
|
|
return this.memory.preferences.findByUserAndCategory(
|
|
user.id,
|
|
category as Parameters<typeof this.memory.preferences.findByUserAndCategory>[1],
|
|
);
|
|
}
|
|
return this.memory.preferences.findByUser(user.id);
|
|
}
|
|
|
|
@Get('preferences/:key')
|
|
async getPreference(@CurrentUser() user: { id: string }, @Param('key') key: string) {
|
|
const pref = await this.memory.preferences.findByUserAndKey(user.id, key);
|
|
if (!pref) throw new NotFoundException('Preference not found');
|
|
return pref;
|
|
}
|
|
|
|
@Post('preferences')
|
|
async upsertPreference(@CurrentUser() user: { id: string }, @Body() dto: UpsertPreferenceDto) {
|
|
return this.memory.preferences.upsert({
|
|
userId: user.id,
|
|
key: dto.key,
|
|
value: dto.value,
|
|
category: dto.category,
|
|
source: dto.source,
|
|
});
|
|
}
|
|
|
|
@Delete('preferences/:key')
|
|
@HttpCode(HttpStatus.NO_CONTENT)
|
|
async removePreference(@CurrentUser() user: { id: string }, @Param('key') key: string) {
|
|
const deleted = await this.memory.preferences.remove(user.id, key);
|
|
if (!deleted) throw new NotFoundException('Preference not found');
|
|
}
|
|
|
|
// ─── Insights ───────────────────────────────────────────────────────
|
|
|
|
@Get('insights')
|
|
async listInsights(@CurrentUser() user: { id: string }, @Query('limit') limit?: string) {
|
|
return this.memory.insights.findByUser(user.id, limit ? Number(limit) : undefined);
|
|
}
|
|
|
|
@Get('insights/:id')
|
|
async getInsight(@CurrentUser() user: { id: string }, @Param('id') id: string) {
|
|
const insight = await this.memory.insights.findById(id, user.id);
|
|
if (!insight) throw new NotFoundException('Insight not found');
|
|
return insight;
|
|
}
|
|
|
|
@Post('insights')
|
|
async createInsight(@CurrentUser() user: { id: string }, @Body() dto: CreateInsightDto) {
|
|
const embedding = this.embeddings.available
|
|
? await this.embeddings.embed(dto.content)
|
|
: undefined;
|
|
|
|
return this.memory.insights.create({
|
|
userId: user.id,
|
|
content: dto.content,
|
|
source: dto.source,
|
|
category: dto.category,
|
|
metadata: dto.metadata,
|
|
embedding: embedding ?? null,
|
|
});
|
|
}
|
|
|
|
@Delete('insights/:id')
|
|
@HttpCode(HttpStatus.NO_CONTENT)
|
|
async removeInsight(@CurrentUser() user: { id: string }, @Param('id') id: string) {
|
|
const deleted = await this.memory.insights.remove(id, user.id);
|
|
if (!deleted) throw new NotFoundException('Insight not found');
|
|
}
|
|
|
|
// ─── Search ─────────────────────────────────────────────────────────
|
|
|
|
@Post('search')
|
|
async searchMemory(@CurrentUser() user: { id: string }, @Body() dto: SearchMemoryDto) {
|
|
if (!this.embeddings.available) {
|
|
return {
|
|
query: dto.query,
|
|
results: [],
|
|
message: 'Semantic search requires OPENAI_API_KEY for embeddings',
|
|
};
|
|
}
|
|
|
|
const queryEmbedding = await this.embeddings.embed(dto.query);
|
|
const results = await this.memory.insights.searchByEmbedding(
|
|
user.id,
|
|
queryEmbedding,
|
|
dto.limit ?? 10,
|
|
dto.maxDistance ?? 0.8,
|
|
);
|
|
|
|
return { query: dto.query, results };
|
|
}
|
|
}
|