mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:41:09 +02:00
feat(ai-scope): wire filterByScope into list_tasks/contacts/events + note tag UI
All major list-returning auto-tools now filter by the ambient agent scope: list_notes (already done), list_tasks (via taskTagTable), get_contacts (via contactTagOps), get_todays_events (via eventTagOps). Untagged records pass through (globally visible); tagged records are only returned when at least one tag matches the agent's scopeTagIds. Notes detail view (/notes/[id]) grows a TagSelector widget between the content textarea and the color picker, powered by the new noteTagOps junction. Users can manually tag notes to scope them to specific agents — complements the AI's add_tag_to_note tool. Also: todo/stores/tags.svelte.ts created (taskLabelOps wrapper around the existing taskLabels junction for the scope filter). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ed01d24f2d
commit
c412508b95
5 changed files with 70 additions and 4 deletions
|
|
@ -4,8 +4,10 @@
|
|||
|
||||
import type { ModuleTool } from '$lib/data/tools/types';
|
||||
import { eventsStore } from './stores/events.svelte';
|
||||
import { eventTagOps } from './stores/tags.svelte';
|
||||
import { db } from '$lib/data/database';
|
||||
import { decryptRecords } from '$lib/data/crypto';
|
||||
import { filterByScope } from '$lib/data/ai/scope-context';
|
||||
import type { LocalTimeBlock } from '$lib/data/time-blocks/types';
|
||||
|
||||
export const calendarTools: ModuleTool[] = [
|
||||
|
|
@ -65,7 +67,10 @@ export const calendarTools: ModuleTool[] = [
|
|||
(b) => !b.deletedAt && b.type === 'event' && b.sourceModule === 'calendar'
|
||||
);
|
||||
const decrypted = await decryptRecords<LocalTimeBlock>('timeBlocks', eventBlocks);
|
||||
const events = decrypted
|
||||
const scoped = await filterByScope(decrypted, async (b) =>
|
||||
b.sourceId ? eventTagOps.getTagIds(b.sourceId) : []
|
||||
);
|
||||
const events = scoped
|
||||
.sort((a, b) => (a.startDate as string).localeCompare(b.startDate as string))
|
||||
.map((b) => ({
|
||||
id: b.sourceId,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import type { ModuleTool } from '$lib/data/tools/types';
|
||||
import { contactsStore } from './stores/contacts.svelte';
|
||||
import { contactTable } from './collections';
|
||||
import { contactTagOps } from './stores/tags.svelte';
|
||||
import { decryptRecords } from '$lib/data/crypto';
|
||||
import { filterByScope } from '$lib/data/ai/scope-context';
|
||||
import { toContact } from './queries';
|
||||
import type { LocalContact } from './types';
|
||||
|
||||
|
|
@ -44,7 +46,8 @@ export const contactsTools: ModuleTool[] = [
|
|||
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);
|
||||
const scoped = await filterByScope(decrypted, async (c) => contactTagOps.getTagIds(c.id));
|
||||
const contacts = scoped.map(toContact);
|
||||
return {
|
||||
success: true,
|
||||
data: contacts.map((c) => ({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Todo Tags — Uses shared global tags via taskLabels junction table.
|
||||
* Note: the junction uses 'labelId' (not 'tagId') for historical reasons.
|
||||
*/
|
||||
|
||||
import { db } from '$lib/data/database';
|
||||
import { createTagLinkOps } from '@mana/shared-stores';
|
||||
|
||||
export {
|
||||
tagMutations,
|
||||
useAllTags,
|
||||
getTagById,
|
||||
getTagsByIds,
|
||||
getTagColor,
|
||||
} from '@mana/shared-stores';
|
||||
|
||||
export const taskLabelOps = createTagLinkOps({
|
||||
table: () => db.table('taskLabels'),
|
||||
entityIdField: 'taskId',
|
||||
});
|
||||
|
|
@ -4,9 +4,10 @@
|
|||
|
||||
import type { ModuleTool } from '$lib/data/tools/types';
|
||||
import { tasksStore } from './stores/tasks.svelte';
|
||||
import { taskTable } from './collections';
|
||||
import { taskTable, taskTagTable } from './collections';
|
||||
import { toTask, getTaskStats } from './queries';
|
||||
import { decryptRecords } from '$lib/data/crypto';
|
||||
import { filterByScope } from '$lib/data/ai/scope-context';
|
||||
import type { LocalTask } from './types';
|
||||
|
||||
export const todoTools: ModuleTool[] = [
|
||||
|
|
@ -94,7 +95,11 @@ export const todoTools: ModuleTool[] = [
|
|||
const all = await taskTable.toArray();
|
||||
const active = all.filter((t) => !t.deletedAt);
|
||||
const decrypted = await decryptRecords<LocalTask>('tasks', active);
|
||||
const tasks = decrypted.map(toTask);
|
||||
const scoped = await filterByScope(decrypted, async (t) => {
|
||||
const links = await taskTagTable.where('taskId').equals(t.id).toArray();
|
||||
return links.filter((l) => !l.deletedAt).map((l) => l.tagId);
|
||||
});
|
||||
const tasks = scoped.map(toTask);
|
||||
|
||||
const filter = (params.filter as string) ?? 'open';
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@
|
|||
import type { Note } from '$lib/modules/notes/types';
|
||||
import { NOTE_COLORS } from '$lib/modules/notes/types';
|
||||
import { notesStore } from '$lib/modules/notes/stores/notes.svelte';
|
||||
import { noteTagOps, useAllTags } from '$lib/modules/notes/stores/tags.svelte';
|
||||
import { formatRelativeTime } from '$lib/modules/notes/queries';
|
||||
import { TagSelector, type Tag } from '@mana/shared-ui';
|
||||
|
||||
const allNotes$: Observable<Note[]> = getContext('notes');
|
||||
let notes = $state<Note[]>([]);
|
||||
|
|
@ -18,6 +20,17 @@
|
|||
|
||||
let noteId = $derived($page.params.id);
|
||||
let note = $derived(notes.find((n) => n.id === noteId));
|
||||
const allTags = $derived(useAllTags());
|
||||
let noteTags = $state<Tag[]>([]);
|
||||
|
||||
// Sync tags when note changes
|
||||
$effect(() => {
|
||||
if (note && noteId) {
|
||||
noteTagOps.getTagIds(noteId).then((ids: string[]) => {
|
||||
noteTags = allTags.value.filter((t) => ids.includes(t.id));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let title = $state('');
|
||||
let content = $state('');
|
||||
|
|
@ -123,6 +136,23 @@
|
|||
oninput={autoSave}
|
||||
></textarea>
|
||||
|
||||
<!-- Tags -->
|
||||
<div class="tag-row">
|
||||
<TagSelector
|
||||
tags={allTags.value}
|
||||
selectedTags={noteTags}
|
||||
onTagsChange={async (tags) => {
|
||||
noteTags = tags;
|
||||
await noteTagOps.setTags(
|
||||
note.id,
|
||||
tags.map((t) => t.id)
|
||||
);
|
||||
}}
|
||||
placeholder="Tags…"
|
||||
addTagLabel="Tag hinzufügen"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Color + Actions -->
|
||||
<div class="detail-footer">
|
||||
<div class="color-row">
|
||||
|
|
@ -247,6 +277,9 @@
|
|||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
|
||||
.tag-row {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
.detail-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue