mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
feat(workbench): scope TagSelector in SceneHeader + agent auto-infer
The SceneHeader (left side of the workbench homepage) now shows a TagSelector directly above the template-gallery button. Users can assign scope tags to the active scene — filtering notes/tasks/contacts/ calendar to only records tagged with those scopes (+ untagged globally visible). Auto-inference: when a scene is bound to an agent via viewingAsAgentId and the scene has no explicit scopeTagIds, the agent's scopeTagIds are used instead. A small "via Agent" hint appears so the user knows the scope comes from the binding. Explicitly setting scope tags on the scene overrides the agent's. New store methods: setSceneScopeTags + updateScene (both sync the reactive scene-scope store when the active scene is affected). patchScene accepts scopeTagIds in its type union. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fe141e193e
commit
57c2bdb2ad
2 changed files with 76 additions and 1 deletions
|
|
@ -12,12 +12,40 @@
|
|||
import { workbenchScenesStore } from '$lib/stores/workbench-scenes.svelte';
|
||||
import { Sparkle } from '@mana/shared-icons';
|
||||
import { goto } from '$app/navigation';
|
||||
import { TagSelector, type Tag } from '@mana/shared-ui';
|
||||
import { useAllTags } from '@mana/shared-stores';
|
||||
import { useAgents } from '$lib/data/ai/agents/queries';
|
||||
|
||||
interface Props {
|
||||
scene: WorkbenchScene | null;
|
||||
}
|
||||
|
||||
const { scene }: Props = $props();
|
||||
const allTags = $derived(useAllTags());
|
||||
const agents = $derived(useAgents());
|
||||
|
||||
// Auto-infer scopeTagIds from bound agent if scene has no explicit override
|
||||
const effectiveScopeTagIds = $derived.by(() => {
|
||||
if (!scene) return [];
|
||||
if (scene.scopeTagIds?.length) return scene.scopeTagIds;
|
||||
if (scene.viewingAsAgentId) {
|
||||
const agent = agents.value.find((a) => a.id === scene.viewingAsAgentId);
|
||||
return agent?.scopeTagIds ?? [];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const selectedScopeTags = $derived<Tag[]>(
|
||||
allTags.value.filter((t) => effectiveScopeTagIds.includes(t.id))
|
||||
);
|
||||
|
||||
async function handleScopeChange(tags: Tag[]) {
|
||||
if (!scene) return;
|
||||
const ids = tags.map((t) => t.id);
|
||||
await workbenchScenesStore.updateScene(scene.id, {
|
||||
scopeTagIds: ids.length > 0 ? ids : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
// The seeded default scene is called "Home". Its empty-description
|
||||
// placeholder gets a welcoming line instead of the generic prompt.
|
||||
|
|
@ -131,6 +159,20 @@
|
|||
onblur={(e) => commitDescription(e.currentTarget, scene.description ?? '')}
|
||||
></p>
|
||||
|
||||
<!-- Scope tags: filter what this scene shows -->
|
||||
<div class="scope-picker">
|
||||
<TagSelector
|
||||
tags={allTags.value}
|
||||
selectedTags={selectedScopeTags}
|
||||
onTagsChange={handleScopeChange}
|
||||
placeholder={effectiveScopeTagIds.length > 0 ? '' : 'Bereiche filtern…'}
|
||||
addTagLabel="Bereich hinzufügen"
|
||||
/>
|
||||
{#if !scene.scopeTagIds?.length && scene.viewingAsAgentId && effectiveScopeTagIds.length > 0}
|
||||
<span class="scope-hint">via Agent</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!--
|
||||
Template-Gallery shortcut — persistent discoverability hook
|
||||
directly on the homepage. Small chip-style, deliberately
|
||||
|
|
@ -222,6 +264,18 @@
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.scope-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
.scope-hint {
|
||||
font-size: 0.6875rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
font-style: italic;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.template-shortcut {
|
||||
align-self: flex-start;
|
||||
margin-top: 0.75rem;
|
||||
|
|
|
|||
|
|
@ -102,7 +102,13 @@ async function patchScene(
|
|||
patch: Partial<
|
||||
Pick<
|
||||
LocalWorkbenchScene,
|
||||
'name' | 'description' | 'openApps' | 'order' | 'wallpaper' | 'viewingAsAgentId'
|
||||
| 'name'
|
||||
| 'description'
|
||||
| 'openApps'
|
||||
| 'order'
|
||||
| 'wallpaper'
|
||||
| 'viewingAsAgentId'
|
||||
| 'scopeTagIds'
|
||||
>
|
||||
>
|
||||
) {
|
||||
|
|
@ -237,6 +243,21 @@ export const workbenchScenesStore = {
|
|||
await patchScene(id, { viewingAsAgentId: agentId });
|
||||
},
|
||||
|
||||
async setSceneScopeTags(id: string, scopeTagIds: string[] | undefined) {
|
||||
await patchScene(id, { scopeTagIds });
|
||||
// Sync reactive scope if this is the active scene
|
||||
if (id === activeSceneIdState) {
|
||||
setSceneScopeTagIds(scopeTagIds);
|
||||
}
|
||||
},
|
||||
|
||||
async updateScene(id: string, patch: Partial<WorkbenchScene>) {
|
||||
await patchScene(id, patch);
|
||||
if (id === activeSceneIdState && patch.scopeTagIds !== undefined) {
|
||||
setSceneScopeTagIds(patch.scopeTagIds);
|
||||
}
|
||||
},
|
||||
|
||||
async duplicateScene(id: string) {
|
||||
const src = scenesState.find((s) => s.id === id);
|
||||
if (!src) return;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue