mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 22:39:40 +02:00
feat(brain): add domain events + tools for habits, journal, notes, contacts, body
Extends the Companion Brain to 10 modules (from 5). Adds semantic domain events and LLM tools for the next 5 most valuable modules. New domain events (15 types): - Habits: HabitLogged, HabitCreated, HabitDeleted - Journal: JournalEntryCreated, JournalMoodSet, JournalEntryDeleted - Notes: NoteCreated, NoteDeleted - Contacts: ContactCreated, ContactDeleted - Body: WorkoutStarted, WorkoutFinished, SetLogged, MeasurementLogged, EnergyCheckLogged New tools (12 tools): - Habits: log_habit, get_habits, create_habit - Journal: create_journal_entry, set_mood - Notes: create_note - Contacts: create_contact, get_contacts - Body: start_workout, finish_workout, log_measurement Totals: 35 event types, 25 tools across 10 modules. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4211ce68da
commit
c51382a76e
12 changed files with 480 additions and 0 deletions
|
|
@ -226,6 +226,118 @@ export type PlacesEventType =
|
|||
| 'TrackingStarted'
|
||||
| 'TrackingStopped';
|
||||
|
||||
// ── Habits ──────────────────────────────────────────
|
||||
|
||||
export interface HabitLoggedPayload {
|
||||
logId: string;
|
||||
habitId: string;
|
||||
habitTitle: string;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export interface HabitCreatedPayload {
|
||||
habitId: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface HabitDeletedPayload {
|
||||
habitId: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export type HabitsEventType = 'HabitLogged' | 'HabitCreated' | 'HabitDeleted';
|
||||
|
||||
// ── Journal ─────────────────────────────────────────
|
||||
|
||||
export interface JournalEntryCreatedPayload {
|
||||
entryId: string;
|
||||
title?: string;
|
||||
mood?: string;
|
||||
hasContent: boolean;
|
||||
}
|
||||
|
||||
export interface JournalMoodSetPayload {
|
||||
entryId: string;
|
||||
mood: string;
|
||||
}
|
||||
|
||||
export interface JournalEntryDeletedPayload {
|
||||
entryId: string;
|
||||
}
|
||||
|
||||
export type JournalEventType = 'JournalEntryCreated' | 'JournalMoodSet' | 'JournalEntryDeleted';
|
||||
|
||||
// ── Notes ───────────────────────────────────────────
|
||||
|
||||
export interface NoteCreatedPayload {
|
||||
noteId: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface NoteDeletedPayload {
|
||||
noteId: string;
|
||||
}
|
||||
|
||||
export type NotesEventType = 'NoteCreated' | 'NoteDeleted';
|
||||
|
||||
// ── Contacts ────────────────────────────────────────
|
||||
|
||||
export interface ContactCreatedPayload {
|
||||
contactId: string;
|
||||
firstName: string;
|
||||
lastName?: string;
|
||||
}
|
||||
|
||||
export interface ContactDeletedPayload {
|
||||
contactId: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type ContactsEventType = 'ContactCreated' | 'ContactDeleted';
|
||||
|
||||
// ── Body ────────────────────────────────────────────
|
||||
|
||||
export interface WorkoutStartedPayload {
|
||||
workoutId: string;
|
||||
title?: string;
|
||||
routineId?: string;
|
||||
}
|
||||
|
||||
export interface WorkoutFinishedPayload {
|
||||
workoutId: string;
|
||||
title: string;
|
||||
durationMinutes: number;
|
||||
setCount: number;
|
||||
}
|
||||
|
||||
export interface SetLoggedPayload {
|
||||
setId: string;
|
||||
workoutId: string;
|
||||
exerciseId: string;
|
||||
reps: number;
|
||||
weight: number;
|
||||
}
|
||||
|
||||
export interface MeasurementLoggedPayload {
|
||||
measurementId: string;
|
||||
type: string;
|
||||
value: number;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export interface EnergyCheckLoggedPayload {
|
||||
checkId: string;
|
||||
energy?: number;
|
||||
mood?: number;
|
||||
}
|
||||
|
||||
export type BodyEventType =
|
||||
| 'WorkoutStarted'
|
||||
| 'WorkoutFinished'
|
||||
| 'SetLogged'
|
||||
| 'MeasurementLogged'
|
||||
| 'EnergyCheckLogged';
|
||||
|
||||
// ── System Events (Goals, Companion) ────────────────
|
||||
|
||||
export interface GoalReachedPayload {
|
||||
|
|
@ -253,6 +365,11 @@ export type ManaEventType =
|
|||
| DrinkEventType
|
||||
| NutriphiEventType
|
||||
| PlacesEventType
|
||||
| HabitsEventType
|
||||
| JournalEventType
|
||||
| NotesEventType
|
||||
| ContactsEventType
|
||||
| BodyEventType
|
||||
| SystemEventType;
|
||||
|
||||
/**
|
||||
|
|
@ -291,6 +408,26 @@ export type ManaEvent =
|
|||
| DomainEvent<'LocationLogged', LocationLoggedPayload>
|
||||
| DomainEvent<'TrackingStarted', TrackingStartedPayload>
|
||||
| DomainEvent<'TrackingStopped', TrackingStoppedPayload>
|
||||
// Habits
|
||||
| DomainEvent<'HabitLogged', HabitLoggedPayload>
|
||||
| DomainEvent<'HabitCreated', HabitCreatedPayload>
|
||||
| DomainEvent<'HabitDeleted', HabitDeletedPayload>
|
||||
// Journal
|
||||
| DomainEvent<'JournalEntryCreated', JournalEntryCreatedPayload>
|
||||
| DomainEvent<'JournalMoodSet', JournalMoodSetPayload>
|
||||
| DomainEvent<'JournalEntryDeleted', JournalEntryDeletedPayload>
|
||||
// Notes
|
||||
| DomainEvent<'NoteCreated', NoteCreatedPayload>
|
||||
| DomainEvent<'NoteDeleted', NoteDeletedPayload>
|
||||
// Contacts
|
||||
| DomainEvent<'ContactCreated', ContactCreatedPayload>
|
||||
| DomainEvent<'ContactDeleted', ContactDeletedPayload>
|
||||
// Body
|
||||
| DomainEvent<'WorkoutStarted', WorkoutStartedPayload>
|
||||
| DomainEvent<'WorkoutFinished', WorkoutFinishedPayload>
|
||||
| DomainEvent<'SetLogged', SetLoggedPayload>
|
||||
| DomainEvent<'MeasurementLogged', MeasurementLoggedPayload>
|
||||
| DomainEvent<'EnergyCheckLogged', EnergyCheckLoggedPayload>
|
||||
// System
|
||||
| DomainEvent<'GoalReached', GoalReachedPayload>
|
||||
| DomainEvent<'GoalProgress', GoalProgressPayload>;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ import { calendarTools } from '$lib/modules/calendar/tools';
|
|||
import { drinkTools } from '$lib/modules/drink/tools';
|
||||
import { nutriphiTools } from '$lib/modules/nutriphi/tools';
|
||||
import { placesTools } from '$lib/modules/places/tools';
|
||||
import { habitsTools } from '$lib/modules/habits/tools';
|
||||
import { journalTools } from '$lib/modules/journal/tools';
|
||||
import { notesTools } from '$lib/modules/notes/tools';
|
||||
import { contactsTools } from '$lib/modules/contacts/tools';
|
||||
import { bodyTools } from '$lib/modules/body/tools';
|
||||
|
||||
let initialized = false;
|
||||
|
||||
|
|
@ -19,5 +24,10 @@ export function initTools(): void {
|
|||
registerTools(drinkTools);
|
||||
registerTools(nutriphiTools);
|
||||
registerTools(placesTools);
|
||||
registerTools(habitsTools);
|
||||
registerTools(journalTools);
|
||||
registerTools(notesTools);
|
||||
registerTools(contactsTools);
|
||||
registerTools(bodyTools);
|
||||
initialized = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
*/
|
||||
|
||||
import { encryptRecord } from '$lib/data/crypto';
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import { createBlock, updateBlock, deleteBlock } from '$lib/data/time-blocks/service';
|
||||
import {
|
||||
bodyExerciseTable,
|
||||
|
|
@ -183,6 +184,11 @@ export const bodyStore = {
|
|||
const snapshot = toBodyWorkout({ ...newLocal });
|
||||
await encryptRecord('bodyWorkouts', newLocal);
|
||||
await bodyWorkoutTable.add(newLocal);
|
||||
emitDomainEvent('WorkoutStarted', 'body', 'bodyWorkouts', workoutId, {
|
||||
workoutId,
|
||||
title,
|
||||
routineId: input.routineId,
|
||||
});
|
||||
return snapshot;
|
||||
},
|
||||
|
||||
|
|
@ -204,6 +210,16 @@ export const bodyStore = {
|
|||
if (workout?.timeBlockId) {
|
||||
await updateBlock(workout.timeBlockId, { endDate: now });
|
||||
}
|
||||
const sets = await bodySetTable.where('workoutId').equals(id).toArray();
|
||||
const durationMs = workout?.startedAt
|
||||
? Date.now() - new Date(workout.startedAt as string).getTime()
|
||||
: 0;
|
||||
emitDomainEvent('WorkoutFinished', 'body', 'bodyWorkouts', id, {
|
||||
workoutId: id,
|
||||
title: (workout?.title as string) ?? 'Workout',
|
||||
durationMinutes: Math.round(durationMs / 60000),
|
||||
setCount: sets.filter((s) => !s.deletedAt).length,
|
||||
});
|
||||
},
|
||||
|
||||
async updateWorkout(
|
||||
|
|
@ -262,6 +278,13 @@ export const bodyStore = {
|
|||
const snapshot = toBodySet({ ...newLocal });
|
||||
await encryptRecord('bodySets', newLocal);
|
||||
await bodySetTable.add(newLocal);
|
||||
emitDomainEvent('SetLogged', 'body', 'bodySets', newLocal.id, {
|
||||
setId: newLocal.id,
|
||||
workoutId: input.workoutId,
|
||||
exerciseId: input.exerciseId,
|
||||
reps: input.reps,
|
||||
weight: input.weight,
|
||||
});
|
||||
return snapshot;
|
||||
},
|
||||
|
||||
|
|
@ -299,6 +322,12 @@ export const bodyStore = {
|
|||
const snapshot = toBodyMeasurement({ ...newLocal });
|
||||
await encryptRecord('bodyMeasurements', newLocal);
|
||||
await bodyMeasurementTable.add(newLocal);
|
||||
emitDomainEvent('MeasurementLogged', 'body', 'bodyMeasurements', newLocal.id, {
|
||||
measurementId: newLocal.id,
|
||||
type: input.type,
|
||||
value: input.value,
|
||||
unit: input.unit,
|
||||
});
|
||||
return snapshot;
|
||||
},
|
||||
|
||||
|
|
@ -361,6 +390,11 @@ export const bodyStore = {
|
|||
const snapshot = toBodyCheck({ ...newLocal });
|
||||
await encryptRecord('bodyChecks', newLocal);
|
||||
await bodyCheckTable.add(newLocal);
|
||||
emitDomainEvent('EnergyCheckLogged', 'body', 'bodyChecks', newLocal.id, {
|
||||
checkId: newLocal.id,
|
||||
energy: input.energy,
|
||||
mood: input.mood,
|
||||
});
|
||||
return snapshot;
|
||||
},
|
||||
|
||||
|
|
|
|||
71
apps/mana/apps/web/src/lib/modules/body/tools.ts
Normal file
71
apps/mana/apps/web/src/lib/modules/body/tools.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import type { ModuleTool } from '$lib/data/tools/types';
|
||||
|
||||
export const bodyTools: ModuleTool[] = [
|
||||
{
|
||||
name: 'start_workout',
|
||||
module: 'body',
|
||||
description: 'Startet ein neues Workout',
|
||||
parameters: [
|
||||
{ name: 'title', type: 'string', description: 'Name des Workouts', required: false },
|
||||
],
|
||||
async execute(params) {
|
||||
const { bodyStore } = await import('./stores/body.svelte');
|
||||
const workout = await bodyStore.startWorkout({
|
||||
title: (params.title as string) ?? 'Workout',
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: workout,
|
||||
message: `Workout "${params.title ?? 'Workout'}" gestartet`,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'finish_workout',
|
||||
module: 'body',
|
||||
description: 'Beendet das aktuelle Workout',
|
||||
parameters: [
|
||||
{ name: 'workoutId', type: 'string', description: 'ID des Workouts', required: true },
|
||||
],
|
||||
async execute(params) {
|
||||
const { bodyStore } = await import('./stores/body.svelte');
|
||||
await bodyStore.finishWorkout(params.workoutId as string);
|
||||
return { success: true, message: 'Workout beendet' };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'log_measurement',
|
||||
module: 'body',
|
||||
description: 'Loggt eine Koerpermessung (Gewicht, Koerperfett, etc.)',
|
||||
parameters: [
|
||||
{
|
||||
name: 'type',
|
||||
type: 'string',
|
||||
description: 'Art der Messung',
|
||||
required: true,
|
||||
enum: ['weight', 'bodyFat', 'chest', 'waist', 'hips', 'biceps', 'thighs'],
|
||||
},
|
||||
{ name: 'value', type: 'number', description: 'Messwert', required: true },
|
||||
{
|
||||
name: 'unit',
|
||||
type: 'string',
|
||||
description: 'Einheit',
|
||||
required: false,
|
||||
enum: ['kg', 'lbs', 'percent', 'cm', 'in'],
|
||||
},
|
||||
],
|
||||
async execute(params) {
|
||||
const { bodyStore } = await import('./stores/body.svelte');
|
||||
const measurement = await bodyStore.logMeasurement({
|
||||
type: params.type as 'weight',
|
||||
value: params.value as number,
|
||||
unit: (params.unit as 'kg') ?? 'kg',
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: measurement,
|
||||
message: `${params.type}: ${params.value} ${params.unit ?? 'kg'}`,
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
@ -10,6 +10,7 @@ import { toContact } from '../queries';
|
|||
import { createArchiveOps } from '@mana/shared-stores';
|
||||
import { ContactsEvents } from '@mana/shared-utils/analytics';
|
||||
import { encryptRecord, decryptRecord } from '$lib/data/crypto';
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import type { LocalContact, Contact } from '../types';
|
||||
import type { UserProfile } from '$lib/api/profile';
|
||||
|
||||
|
|
@ -47,6 +48,11 @@ export const contactsStore = {
|
|||
const plaintextSnapshot = toContact(newLocal);
|
||||
await encryptRecord('contacts', newLocal);
|
||||
await contactTable.add(newLocal);
|
||||
emitDomainEvent('ContactCreated', 'contacts', 'contacts', newLocal.id, {
|
||||
contactId: newLocal.id,
|
||||
firstName: data.firstName ?? '',
|
||||
lastName: data.lastName,
|
||||
});
|
||||
ContactsEvents.contactCreated();
|
||||
return plaintextSnapshot;
|
||||
},
|
||||
|
|
@ -89,10 +95,16 @@ export const contactsStore = {
|
|||
},
|
||||
|
||||
async deleteContact(id: string) {
|
||||
const local = await contactTable.get(id);
|
||||
const decrypted = local ? await decryptRecord('contacts', { ...local }) : null;
|
||||
await contactTable.update(id, {
|
||||
deletedAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
emitDomainEvent('ContactDeleted', 'contacts', 'contacts', id, {
|
||||
contactId: id,
|
||||
name: [decrypted?.firstName, decrypted?.lastName].filter(Boolean).join(' ') || '',
|
||||
});
|
||||
ContactsEvents.contactDeleted();
|
||||
},
|
||||
|
||||
|
|
|
|||
50
apps/mana/apps/web/src/lib/modules/contacts/tools.ts
Normal file
50
apps/mana/apps/web/src/lib/modules/contacts/tools.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import type { ModuleTool } from '$lib/data/tools/types';
|
||||
import { contactsStore } from './stores/contacts.svelte';
|
||||
import { contactTable } from './collections';
|
||||
import { decryptRecords } from '$lib/data/crypto';
|
||||
import { toContact } from './queries';
|
||||
import type { LocalContact } from './types';
|
||||
|
||||
export const contactsTools: ModuleTool[] = [
|
||||
{
|
||||
name: 'create_contact',
|
||||
module: 'contacts',
|
||||
description: 'Erstellt einen neuen Kontakt',
|
||||
parameters: [
|
||||
{ name: 'firstName', type: 'string', description: 'Vorname', required: true },
|
||||
{ name: 'lastName', type: 'string', description: 'Nachname', required: false },
|
||||
{ name: 'email', type: 'string', description: 'E-Mail', required: false },
|
||||
{ name: 'phone', type: 'string', description: 'Telefon', required: false },
|
||||
],
|
||||
async execute(params) {
|
||||
const contact = await contactsStore.createContact({
|
||||
firstName: params.firstName as string,
|
||||
lastName: params.lastName as string | undefined,
|
||||
email: params.email as string | undefined,
|
||||
phone: params.phone as string | undefined,
|
||||
});
|
||||
return { success: true, data: contact, message: `Kontakt "${params.firstName}" erstellt` };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'get_contacts',
|
||||
module: 'contacts',
|
||||
description: 'Gibt alle Kontakte zurueck',
|
||||
parameters: [],
|
||||
async execute() {
|
||||
const all = await contactTable.toArray();
|
||||
const active = all.filter((c) => !c.deletedAt && !c.isArchived);
|
||||
const decrypted = await decryptRecords<LocalContact>('contacts', active);
|
||||
const contacts = decrypted.map(toContact);
|
||||
return {
|
||||
success: true,
|
||||
data: contacts.map((c) => ({
|
||||
id: c.id,
|
||||
name: [c.firstName, c.lastName].filter(Boolean).join(' '),
|
||||
company: c.company,
|
||||
})),
|
||||
message: `${contacts.length} Kontakte`,
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
* All reads are handled by liveQuery hooks in queries.ts.
|
||||
*/
|
||||
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import { habitTable, habitLogTable } from '../collections';
|
||||
import { toHabit } from '../queries';
|
||||
import {
|
||||
|
|
@ -101,6 +102,10 @@ export const habitsStore = {
|
|||
};
|
||||
|
||||
await habitTable.add(newLocal);
|
||||
emitDomainEvent('HabitCreated', 'habits', 'habits', newLocal.id, {
|
||||
habitId: newLocal.id,
|
||||
title: data.title,
|
||||
});
|
||||
return toHabit(newLocal);
|
||||
},
|
||||
|
||||
|
|
@ -120,10 +125,15 @@ export const habitsStore = {
|
|||
},
|
||||
|
||||
async deleteHabit(id: string) {
|
||||
const habit = await habitTable.get(id);
|
||||
await habitTable.update(id, {
|
||||
deletedAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
emitDomainEvent('HabitDeleted', 'habits', 'habits', id, {
|
||||
habitId: id,
|
||||
title: habit?.title ?? '',
|
||||
});
|
||||
// Also soft-delete all logs and their timeBlocks
|
||||
const logs = await habitLogTable.where('habitId').equals(id).toArray();
|
||||
const now = new Date().toISOString();
|
||||
|
|
@ -233,6 +243,12 @@ export const habitsStore = {
|
|||
};
|
||||
|
||||
await habitLogTable.add(newLog);
|
||||
emitDomainEvent('HabitLogged', 'habits', 'habitLogs', logId, {
|
||||
logId,
|
||||
habitId,
|
||||
habitTitle: habit?.title ?? '',
|
||||
note,
|
||||
});
|
||||
return newLog;
|
||||
},
|
||||
|
||||
|
|
|
|||
55
apps/mana/apps/web/src/lib/modules/habits/tools.ts
Normal file
55
apps/mana/apps/web/src/lib/modules/habits/tools.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import type { ModuleTool } from '$lib/data/tools/types';
|
||||
import { habitsStore } from './stores/habits.svelte';
|
||||
import { habitTable } from './collections';
|
||||
|
||||
export const habitsTools: ModuleTool[] = [
|
||||
{
|
||||
name: 'log_habit',
|
||||
module: 'habits',
|
||||
description: 'Loggt ein Habit (z.B. Sport, Meditation, Lesen)',
|
||||
parameters: [
|
||||
{ name: 'habitId', type: 'string', description: 'ID des Habits', required: true },
|
||||
{ name: 'note', type: 'string', description: 'Optionale Notiz', required: false },
|
||||
],
|
||||
async execute(params) {
|
||||
const log = await habitsStore.logHabit(
|
||||
params.habitId as string,
|
||||
params.note as string | undefined
|
||||
);
|
||||
return { success: true, data: log, message: 'Habit geloggt' };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'get_habits',
|
||||
module: 'habits',
|
||||
description: 'Gibt alle aktiven Habits zurueck',
|
||||
parameters: [],
|
||||
async execute() {
|
||||
const all = await habitTable.toArray();
|
||||
const active = all.filter((h) => !h.deletedAt && !h.isArchived);
|
||||
return {
|
||||
success: true,
|
||||
data: active.map((h) => ({ id: h.id, title: h.title, icon: h.icon, color: h.color })),
|
||||
message: `${active.length} Habits`,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'create_habit',
|
||||
module: 'habits',
|
||||
description: 'Erstellt ein neues Habit',
|
||||
parameters: [
|
||||
{ name: 'title', type: 'string', description: 'Name des Habits', required: true },
|
||||
{ name: 'icon', type: 'string', description: 'Emoji-Icon', required: false },
|
||||
{ name: 'color', type: 'string', description: 'Hex-Farbe', required: false },
|
||||
],
|
||||
async execute(params) {
|
||||
const habit = await habitsStore.createHabit({
|
||||
title: params.title as string,
|
||||
icon: (params.icon as string) ?? 'star',
|
||||
color: (params.color as string) ?? '#6366f1',
|
||||
});
|
||||
return { success: true, data: habit, message: `Habit "${habit.title}" erstellt` };
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
import { journalEntryTable } from '../collections';
|
||||
import { toJournalEntry } from '../queries';
|
||||
import { encryptRecord } from '$lib/data/crypto';
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import { transcribeAudio } from '$lib/voice/transcribe';
|
||||
import type { JournalEntry, JournalMood, LocalJournalEntry } from '../types';
|
||||
|
||||
|
|
@ -48,6 +49,12 @@ export const journalStore = {
|
|||
const plaintextSnapshot = toJournalEntry(newLocal);
|
||||
await encryptRecord('journalEntries', newLocal);
|
||||
await journalEntryTable.add(newLocal);
|
||||
emitDomainEvent('JournalEntryCreated', 'journal', 'journalEntries', newLocal.id, {
|
||||
entryId: newLocal.id,
|
||||
title: data.title ?? undefined,
|
||||
mood: data.mood ?? undefined,
|
||||
hasContent: content.length > 0,
|
||||
});
|
||||
return plaintextSnapshot;
|
||||
},
|
||||
|
||||
|
|
@ -128,6 +135,7 @@ export const journalStore = {
|
|||
deletedAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
emitDomainEvent('JournalEntryDeleted', 'journal', 'journalEntries', id, { entryId: id });
|
||||
},
|
||||
|
||||
async togglePin(id: string) {
|
||||
|
|
@ -153,6 +161,8 @@ export const journalStore = {
|
|||
mood,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
if (mood)
|
||||
emitDomainEvent('JournalMoodSet', 'journal', 'journalEntries', id, { entryId: id, mood });
|
||||
},
|
||||
|
||||
async archiveEntry(id: string) {
|
||||
|
|
|
|||
54
apps/mana/apps/web/src/lib/modules/journal/tools.ts
Normal file
54
apps/mana/apps/web/src/lib/modules/journal/tools.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import type { ModuleTool } from '$lib/data/tools/types';
|
||||
import { journalStore } from './stores/journal.svelte';
|
||||
import type { JournalMood } from './types';
|
||||
|
||||
const MOOD_ENUM = [
|
||||
'dankbar',
|
||||
'glücklich',
|
||||
'zufrieden',
|
||||
'neutral',
|
||||
'nachdenklich',
|
||||
'traurig',
|
||||
'gestresst',
|
||||
'wütend',
|
||||
];
|
||||
|
||||
export const journalTools: ModuleTool[] = [
|
||||
{
|
||||
name: 'create_journal_entry',
|
||||
module: 'journal',
|
||||
description:
|
||||
'Erstellt einen neuen Journal-Eintrag mit optionaler Stimmung (dankbar, glücklich, zufrieden, neutral, nachdenklich, traurig, gestresst, wütend)',
|
||||
parameters: [
|
||||
{ name: 'content', type: 'string', description: 'Inhalt des Eintrags', required: true },
|
||||
{ name: 'title', type: 'string', description: 'Optionaler Titel', required: false },
|
||||
{ name: 'mood', type: 'string', description: 'Stimmung', required: false, enum: MOOD_ENUM },
|
||||
],
|
||||
async execute(params) {
|
||||
const entry = await journalStore.createEntry({
|
||||
content: params.content as string,
|
||||
title: params.title as string | undefined,
|
||||
mood: params.mood as JournalMood | undefined,
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: entry,
|
||||
message: `Journal-Eintrag erstellt${params.mood ? ` (Stimmung: ${params.mood})` : ''}`,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'set_mood',
|
||||
module: 'journal',
|
||||
description: 'Erstellt einen Journal-Eintrag mit Stimmung',
|
||||
parameters: [
|
||||
{ name: 'mood', type: 'string', description: 'Stimmung', required: true, enum: MOOD_ENUM },
|
||||
],
|
||||
async execute(params) {
|
||||
const entry = await journalStore.createEntry({
|
||||
mood: params.mood as JournalMood,
|
||||
});
|
||||
return { success: true, data: entry, message: `Stimmung: ${params.mood}` };
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
@ -18,6 +18,7 @@ import { noteTable } from '../collections';
|
|||
import { toNote } from '../queries';
|
||||
import type { LocalNote, Note } from '../types';
|
||||
import { encryptRecord } from '$lib/data/crypto';
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import { transcribeAudio } from '$lib/voice/transcribe';
|
||||
|
||||
export const notesStore = {
|
||||
|
|
@ -36,6 +37,10 @@ export const notesStore = {
|
|||
const plaintextSnapshot = toNote(newLocal);
|
||||
await encryptRecord('notes', newLocal);
|
||||
await noteTable.add(newLocal);
|
||||
emitDomainEvent('NoteCreated', 'notes', 'notes', newLocal.id, {
|
||||
noteId: newLocal.id,
|
||||
title: data.title,
|
||||
});
|
||||
return plaintextSnapshot;
|
||||
},
|
||||
|
||||
|
|
@ -103,6 +108,7 @@ export const notesStore = {
|
|||
deletedAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
emitDomainEvent('NoteDeleted', 'notes', 'notes', id, { noteId: id });
|
||||
},
|
||||
|
||||
async togglePin(id: string) {
|
||||
|
|
|
|||
25
apps/mana/apps/web/src/lib/modules/notes/tools.ts
Normal file
25
apps/mana/apps/web/src/lib/modules/notes/tools.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import type { ModuleTool } from '$lib/data/tools/types';
|
||||
import { notesStore } from './stores/notes.svelte';
|
||||
|
||||
export const notesTools: ModuleTool[] = [
|
||||
{
|
||||
name: 'create_note',
|
||||
module: 'notes',
|
||||
description: 'Erstellt eine neue Notiz',
|
||||
parameters: [
|
||||
{ name: 'title', type: 'string', description: 'Titel', required: false },
|
||||
{ name: 'content', type: 'string', description: 'Inhalt', required: true },
|
||||
],
|
||||
async execute(params) {
|
||||
const note = await notesStore.createNote({
|
||||
title: params.title as string | undefined,
|
||||
content: params.content as string,
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: note,
|
||||
message: `Notiz "${note.title || 'Unbenannt'}" erstellt`,
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
Loading…
Add table
Add a link
Reference in a new issue