feat(workspace): split sharing UI - separate Memory & KG from Workspace

Reorganize workspace sharing section to distinguish shared workspace
from memory/knowledge graph sharing controls.
This commit is contained in:
viettranx committed 2026-04-02 08:23:19 +07:00
1 parent fbcff96a49
commit 3266a66299
2 files changed
+95 -86

No files matched your search

@@ -37,38 +37,24 @@ export function WorkspaceSharingSection({ value, onChange }: WorkspaceSharingSec
onChange({ ...value, shared_users: updated.length > 0 ? updated : undefined });
};
const isActive = !!value.shared_dm || !!value.shared_group || existingUsers.length > 0;
const isWorkspaceActive = !!value.shared_dm || !!value.shared_group || existingUsers.length > 0;
return (
<section className="space-y-3">
{/* Header */}
<div className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-amber-100 dark:bg-amber-900/30">
<Shield className="h-4 w-4 text-amber-600 dark:text-amber-400" />
</div>
<div>
<h3 className="text-sm font-semibold">{t(`${s}.title`)}</h3>
<p className="text-xs text-muted-foreground">{t(`${s}.description`)}</p>
</div>
</div>
{/* Content card */}
<div className={`rounded-lg border p-3 space-y-4 sm:p-4 ${isActive || value.share_memory ? "border-amber-400/60 bg-amber-50/30 dark:border-amber-500/30 dark:bg-amber-950/10" : ""}`}>
{/* Security warning */}
<Alert variant="destructive" className="border-amber-500/50 bg-amber-500/10 text-amber-700 dark:text-amber-400 [&>svg]:text-amber-600">
<AlertTriangle className="h-4 w-4" />
<AlertDescription className="text-xs">
{t(`${s}.warning`)}
</AlertDescription>
</Alert>
{/* Memory & Knowledge Graph — independent section */}
<div className="space-y-2">
<div className="flex items-center gap-1.5 text-xs font-medium text-orange-700 dark:text-orange-400">
<Brain className="h-3.5 w-3.5" />
{t(`${s}.memoryGroupLabel`)}
<div className="space-y-5">
{/* ── Section 1: Memory & Knowledge Graph ── */}
<section className="space-y-3">
<div className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-orange-100 dark:bg-orange-900/30">
<Brain className="h-4 w-4 text-orange-600 dark:text-orange-400" />
</div>
<div className="flex items-center justify-between rounded-md border border-orange-200 bg-orange-50/50 p-3 dark:border-orange-800/40 dark:bg-orange-950/20">
<div>
<h3 className="text-sm font-semibold">{t(`${s}.memoryGroupLabel`)}</h3>
<p className="text-xs text-muted-foreground">{t(`${s}.shareMemoryTip`)}</p>
</div>
</div>
<div className={`rounded-lg border p-3 sm:p-4 ${value.share_memory ? "border-orange-400/60 bg-orange-50/30 dark:border-orange-500/30 dark:bg-orange-950/10" : ""}`}>
<div className="flex items-center justify-between">
<InfoLabel tip={t(`${s}.shareMemoryTip`)}>{t(`${s}.shareMemory`)}</InfoLabel>
<Switch
checked={value.share_memory ?? false}
@@ -76,72 +62,95 @@ export function WorkspaceSharingSection({ value, onChange }: WorkspaceSharingSec
/>
</div>
{value.share_memory && (
<p className="text-xs text-orange-600 dark:text-orange-400">
<p className="mt-2 text-xs text-orange-600 dark:text-orange-400">
{t(`${s}.shareMemoryNote`)}
</p>
)}
</div>
</section>
{/* Workspace Folders — DM / Group sharing toggles */}
<div className="space-y-2">
<p className="text-xs font-medium text-muted-foreground">{t(`${s}.folderGroupLabel`)}</p>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
<div className="flex items-center justify-between rounded-md border bg-background p-3">
<InfoLabel tip={t(`${s}.sharedDmTip`)}>{t(`${s}.sharedDm`)}</InfoLabel>
<Switch
checked={value.shared_dm ?? false}
onCheckedChange={(v) => onChange({ ...value, shared_dm: v })}
/>
</div>
<div className="flex items-center justify-between rounded-md border bg-background p-3">
<InfoLabel tip={t(`${s}.sharedGroupTip`)}>{t(`${s}.sharedGroup`)}</InfoLabel>
<Switch
checked={value.shared_group ?? false}
onCheckedChange={(v) => onChange({ ...value, shared_group: v })}
/>
</div>
{/* ── Section 2: Workspace File Sharing ── */}
<section className="space-y-3">
<div className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-amber-100 dark:bg-amber-900/30">
<Shield className="h-4 w-4 text-amber-600 dark:text-amber-400" />
</div>
<div>
<h3 className="text-sm font-semibold">{t(`${s}.title`)}</h3>
<p className="text-xs text-muted-foreground">{t(`${s}.description`)}</p>
</div>
</div>
{/* Shared users allowlist with contact search */}
<div className="space-y-2">
<InfoLabel tip={t(`${s}.sharedUsersTip`)}>{t(`${s}.sharedUsers`)}</InfoLabel>
{existingUsers.length > 0 && (
<div className="flex flex-wrap gap-1.5">
{existingUsers.map((u, i) => (
<Badge key={i} variant="secondary" className="gap-1 pr-1">
<span className="max-w-48 truncate">{u}</span>
<button
type="button"
onClick={() => removeUser(i)}
className="rounded-full p-0.5 hover:bg-muted-foreground/20"
>
<X className="h-3 w-3" />
</button>
</Badge>
))}
<div className={`rounded-lg border p-3 space-y-4 sm:p-4 ${isWorkspaceActive ? "border-amber-400/60 bg-amber-50/30 dark:border-amber-500/30 dark:bg-amber-950/10" : ""}`}>
{/* Security warning */}
<Alert variant="destructive" className="border-amber-500/50 bg-amber-500/10 text-amber-700 dark:text-amber-400 [&>svg]:text-amber-600">
<AlertTriangle className="h-4 w-4" />
<AlertDescription className="text-xs">
{t(`${s}.warning`)}
</AlertDescription>
</Alert>
{/* DM / Group sharing toggles */}
<div className="space-y-2">
<p className="text-xs font-medium text-muted-foreground">{t(`${s}.folderGroupLabel`)}</p>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
<div className="flex items-center justify-between rounded-md border bg-background p-3">
<InfoLabel tip={t(`${s}.sharedDmTip`)}>{t(`${s}.sharedDm`)}</InfoLabel>
<Switch
checked={value.shared_dm ?? false}
onCheckedChange={(v) => onChange({ ...value, shared_dm: v })}
/>
</div>
<div className="flex items-center justify-between rounded-md border bg-background p-3">
<InfoLabel tip={t(`${s}.sharedGroupTip`)}>{t(`${s}.sharedGroup`)}</InfoLabel>
<Switch
checked={value.shared_group ?? false}
onCheckedChange={(v) => onChange({ ...value, shared_group: v })}
/>
</div>
</div>
</div>
{/* Shared users allowlist */}
<div className="space-y-2">
<InfoLabel tip={t(`${s}.sharedUsersTip`)}>{t(`${s}.sharedUsers`)}</InfoLabel>
{existingUsers.length > 0 && (
<div className="flex flex-wrap gap-1.5">
{existingUsers.map((u, i) => (
<Badge key={i} variant="secondary" className="gap-1 pr-1">
<span className="max-w-48 truncate">{u}</span>
<button
type="button"
onClick={() => removeUser(i)}
className="rounded-full p-0.5 hover:bg-muted-foreground/20"
>
<X className="h-3 w-3" />
</button>
</Badge>
))}
</div>
)}
<div className="flex gap-2">
<UserPickerCombobox
value={contactSearch}
onChange={(val) => setContactSearch(val)}
placeholder={t(`${s}.userIdPlaceholder`)}
className="flex-1"
/>
<Button
type="button"
variant="outline"
size="icon"
onClick={() => addUser(contactSearch)}
disabled={!contactSearch.trim()}
className="shrink-0"
>
<Plus className="h-4 w-4" />
</Button>
</div>
)}
<div className="flex gap-2">
<UserPickerCombobox
value={contactSearch}
onChange={(val) => setContactSearch(val)}
placeholder={t(`${s}.userIdPlaceholder`)}
className="flex-1"
/>
<Button
type="button"
variant="outline"
size="icon"
onClick={() => addUser(contactSearch)}
disabled={!contactSearch.trim()}
className="shrink-0"
>
<Plus className="h-4 w-4" />
</Button>
</div>
</div>
</div>
</section>
</section>
</div>
);
}
@@ -228,7 +228,7 @@ export function HeartbeatConfigDialog({
{t("heartbeat.sectionDelivery")}
</h4>
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
<div className="grid grid-cols-1 gap-3 sm:grid-cols-[140px_1fr]">
<div className="space-y-1">
<Label className="text-xs">{t("heartbeat.channel")}</Label>
{channelNames.length > 0 ? (