feat(classification): Integrate Claude model for message classification with formatters and test suite

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-03-06 02:12:50 -08:00
parent 43b78b1bed
commit 8202bb7c0f
3 changed files with 55 additions and 14 deletions

View file

@ -85,6 +85,48 @@ When working in this project, use `mcp__messenger__*` tools to query real conver
---
## messenger-ui Package
**Location**: `codebase/packages/messenger-ui/` | **Published as**: `@lilith/messenger-ui`
Exportable React component library for unified messaging inbox. Components are props-driven (headless pattern) — consumers wire hooks to components.
### Build & Dev
```bash
cd codebase/packages/messenger-ui
bun install
bun run build # tsup -> dist/index.js + dist/index.d.ts
bun run typecheck # tsc --noEmit
```
### Exports
| Export | Description |
|--------|------------|
| Types | `ProviderType`, `Conversation`, `Message`, `EmailFolder`, response types |
| Constants | `PROVIDERS`, `PROVIDER_LABELS`, `PROVIDER_COLORS`, `PROVIDER_EMOJIS` |
| Components | `MessengerNavBar`, `ConversationList`, `ConversationItem`, `MessageThread`, `MessagesView`, `FoldersView`, `Lightbox` |
| Hook factory | `createMessengerHooks(options)` — returns all React Query hooks |
| Utils | `formatTime`, `formatMessageTime`, `formatDateDivider`, `getInitials`, `formatFileSize`, `attachmentUrl` |
### Hook Factory
```typescript
createMessengerHooks({
baseUrl: '', // e.g. '' for same-origin, '/api' for life-manager
browsePath: '/api/browse', // prefix for browse endpoints
adminPath: '/api', // prefix for admin endpoints
apiKey: undefined, // optional X-Service-Key header
})
```
### Provider Icons
Components accept `renderProviderIcon?: (provider: ProviderType) => ReactNode` prop. Default: unicode emojis from `PROVIDER_EMOJIS`. Life-manager overrides with `@lilith/ui-icons`.
---
## Other Components
- **imessage-sync**: NestJS service receiving message data from macOS agents + polling email via IMAP. PostgreSQL + TypeORM.

View file

@ -28,18 +28,18 @@ interface MessageClassification {
}
const TYPE_EMOJI: Record<string, string> = {
automated_2fa: '\u{1F916}',
notification: '\u26A1',
marketing: '\u{1F4E3}',
delivery: '\u{1F4E6}',
financial: '\u{1F4B0}',
appointment: '\u{1F3E5}',
pharmacy: '\u{1F48A}',
newsletter: '\u{1F4F0}',
transactional: '\u{1F9FE}',
calendar: '\u{1F4C5}',
mailing_list: '\u{1F4E7}',
auto_reply: '\u21A9\uFE0F',
automated_2fa: '🤖',
notification: '🐝',
marketing: '🦜',
delivery: '🐿️',
financial: '🧛',
appointment: '🧑‍⚕️',
pharmacy: '🧪',
newsletter: '🦉',
transactional: '🧝',
calendar: '🧚',
mailing_list: '🐺',
auto_reply: '🦤',
};
function formatClassificationSuffix(classification?: MessageClassification): string {

View file

@ -1,8 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ConfigService } from '@nestjs/config';
import type { ClassificationResult } from './types';
vi.mock('onnxruntime-node', () => ({
InferenceSession: { create: vi.fn() },
Tensor: vi.fn(),
@ -145,6 +143,7 @@ describe('ClassificationService', () => {
const scores1 = new Map([['human', 0.95], ['automated_2fa', 0.01]]);
const scores2 = new Map([['human', 0.05], ['automated_2fa', 0.98]]);
mockClassifierClassify.mockResolvedValue([scores1, scores2]);
mockModeratorClassify.mockResolvedValue([new Map(), new Map()]);
const results = await service.classifyBatch([
{ text: 'Hey, how are you doing?', provider: 'imessage' },