mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-18 20:14:43 +00:00
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:
1 parent
fbcff96a49
commit
3266a66299
2 files changed
+95
-86
No files matched your search
+94
-85
@@ -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 ? (
|
||||
|
||||
Reference in new issue
Block a user