@sero-ai/ui 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +615 -0
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +58 -0
- package/dist/components/context-editor/CapabilitySection.cjs +57 -0
- package/dist/components/context-editor/CapabilitySection.d.cts +20 -0
- package/dist/components/context-editor/CapabilitySection.d.ts +20 -0
- package/dist/components/context-editor/CapabilitySection.js +57 -0
- package/dist/components/context-editor/ContextEditor.cjs +108 -0
- package/dist/components/context-editor/ContextEditor.d.cts +26 -0
- package/dist/components/context-editor/ContextEditor.d.ts +26 -0
- package/dist/components/context-editor/ContextEditor.js +108 -0
- package/dist/components/context-editor/PresetBar.cjs +71 -0
- package/dist/components/context-editor/PresetBar.d.cts +19 -0
- package/dist/components/context-editor/PresetBar.d.ts +19 -0
- package/dist/components/context-editor/PresetBar.js +71 -0
- package/dist/components/context-editor/SkillsSection.cjs +31 -0
- package/dist/components/context-editor/SkillsSection.d.cts +14 -0
- package/dist/components/context-editor/SkillsSection.d.ts +14 -0
- package/dist/components/context-editor/SkillsSection.js +31 -0
- package/dist/components/context-editor/SystemPromptSection.cjs +58 -0
- package/dist/components/context-editor/SystemPromptSection.d.cts +25 -0
- package/dist/components/context-editor/SystemPromptSection.d.ts +25 -0
- package/dist/components/context-editor/SystemPromptSection.js +58 -0
- package/dist/components/context-editor/ToolsSection.cjs +31 -0
- package/dist/components/context-editor/ToolsSection.d.cts +14 -0
- package/dist/components/context-editor/ToolsSection.d.ts +14 -0
- package/dist/components/context-editor/ToolsSection.js +31 -0
- package/dist/components/context-editor/index.cjs +8 -0
- package/dist/components/context-editor/index.d.cts +5 -0
- package/dist/components/context-editor/index.d.ts +5 -0
- package/dist/components/context-editor/index.js +8 -0
- package/dist/components/context-editor/parts.cjs +184 -0
- package/dist/components/context-editor/parts.d.cts +32 -0
- package/dist/components/context-editor/parts.d.ts +32 -0
- package/dist/components/context-editor/parts.js +184 -0
- package/dist/components/context-editor/use-context-editor-model.cjs +194 -0
- package/dist/components/context-editor/use-context-editor-model.d.cts +57 -0
- package/dist/components/context-editor/use-context-editor-model.d.ts +57 -0
- package/dist/components/context-editor/use-context-editor-model.js +194 -0
- package/dist/components/dashboard/activity-list.cjs +59 -0
- package/dist/components/dashboard/activity-list.d.cts +27 -0
- package/dist/components/dashboard/activity-list.d.ts +27 -0
- package/dist/components/dashboard/activity-list.js +59 -0
- package/dist/components/dashboard/catalog.cjs +1 -0
- package/dist/components/dashboard/catalog.d.cts +13 -0
- package/dist/components/dashboard/catalog.d.ts +13 -0
- package/dist/components/dashboard/catalog.js +0 -0
- package/dist/components/dashboard/catalog.json +312 -0
- package/dist/components/dashboard/data-boundary.cjs +29 -0
- package/dist/components/dashboard/data-boundary.d.cts +21 -0
- package/dist/components/dashboard/data-boundary.d.ts +21 -0
- package/dist/components/dashboard/data-boundary.js +29 -0
- package/dist/components/dashboard/empty-state.cjs +49 -0
- package/dist/components/dashboard/empty-state.d.cts +20 -0
- package/dist/components/dashboard/empty-state.d.ts +20 -0
- package/dist/components/dashboard/empty-state.js +49 -0
- package/dist/components/dashboard/icon-button.cjs +40 -0
- package/dist/components/dashboard/icon-button.d.cts +24 -0
- package/dist/components/dashboard/icon-button.d.ts +24 -0
- package/dist/components/dashboard/icon-button.js +40 -0
- package/dist/components/dashboard/index.cjs +15 -0
- package/dist/components/dashboard/index.d.cts +24 -0
- package/dist/components/dashboard/index.d.ts +24 -0
- package/dist/components/dashboard/index.js +15 -0
- package/dist/components/dashboard/item-list.cjs +71 -0
- package/dist/components/dashboard/item-list.d.cts +28 -0
- package/dist/components/dashboard/item-list.d.ts +28 -0
- package/dist/components/dashboard/item-list.js +71 -0
- package/dist/components/dashboard/key-value.cjs +39 -0
- package/dist/components/dashboard/key-value.d.cts +17 -0
- package/dist/components/dashboard/key-value.d.ts +17 -0
- package/dist/components/dashboard/key-value.js +39 -0
- package/dist/components/dashboard/layout.cjs +193 -0
- package/dist/components/dashboard/layout.d.cts +91 -0
- package/dist/components/dashboard/layout.d.ts +91 -0
- package/dist/components/dashboard/layout.js +193 -0
- package/dist/components/dashboard/metric.cjs +98 -0
- package/dist/components/dashboard/metric.d.cts +44 -0
- package/dist/components/dashboard/metric.d.ts +44 -0
- package/dist/components/dashboard/metric.js +98 -0
- package/dist/components/dashboard/progress-ring.cjs +77 -0
- package/dist/components/dashboard/progress-ring.d.cts +25 -0
- package/dist/components/dashboard/progress-ring.d.ts +25 -0
- package/dist/components/dashboard/progress-ring.js +77 -0
- package/dist/components/dashboard/skeletons.cjs +54 -0
- package/dist/components/dashboard/skeletons.d.cts +15 -0
- package/dist/components/dashboard/skeletons.d.ts +15 -0
- package/dist/components/dashboard/skeletons.js +54 -0
- package/dist/components/dashboard/spacing.cjs +10 -0
- package/dist/components/dashboard/spacing.d.cts +4 -0
- package/dist/components/dashboard/spacing.d.ts +4 -0
- package/dist/components/dashboard/spacing.js +10 -0
- package/dist/components/dashboard/stale-indicator.cjs +42 -0
- package/dist/components/dashboard/stale-indicator.d.cts +15 -0
- package/dist/components/dashboard/stale-indicator.d.ts +15 -0
- package/dist/components/dashboard/stale-indicator.js +42 -0
- package/dist/components/dashboard/status.cjs +66 -0
- package/dist/components/dashboard/status.d.cts +15 -0
- package/dist/components/dashboard/status.d.ts +15 -0
- package/dist/components/dashboard/status.js +66 -0
- package/dist/components/dashboard/tone.cjs +34 -0
- package/dist/components/dashboard/tone.d.cts +15 -0
- package/dist/components/dashboard/tone.d.ts +15 -0
- package/dist/components/dashboard/tone.js +34 -0
- package/dist/components/dashboard/typography.cjs +99 -0
- package/dist/components/dashboard/typography.d.cts +57 -0
- package/dist/components/dashboard/typography.d.ts +57 -0
- package/dist/components/dashboard/typography.js +99 -0
- package/dist/components/ui/badge.cjs +5 -1
- package/dist/components/ui/badge.d.cts +1 -1
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/components/ui/badge.js +5 -1
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1 -0
- package/dist/reference.cjs +1260 -0
- package/dist/reference.d.cts +11 -0
- package/dist/reference.d.ts +11 -0
- package/dist/reference.js +1230 -0
- package/dist/styles/glass-board.css +43 -0
- package/dist/styles/globals.css +59 -0
- package/package.json +16 -4
- package/scripts/ensure-pnpm-publish.mjs +23 -0
- package/scripts/smoke-dist.mjs +68 -0
- package/src/components/context-editor/CapabilitySection.tsx +85 -0
- package/src/components/context-editor/ContextEditor.tsx +138 -0
- package/src/components/context-editor/PresetBar.tsx +94 -0
- package/src/components/context-editor/SkillsSection.tsx +37 -0
- package/src/components/context-editor/SystemPromptSection.tsx +88 -0
- package/src/components/context-editor/ToolsSection.tsx +37 -0
- package/src/components/context-editor/index.ts +7 -0
- package/src/components/context-editor/parts.tsx +217 -0
- package/src/components/context-editor/use-context-editor-model.ts +296 -0
- package/src/components/dashboard/__tests__/catalog.test.ts +62 -0
- package/src/components/dashboard/__tests__/glass.test.tsx +42 -0
- package/src/components/dashboard/__tests__/state.test.tsx +127 -0
- package/src/components/dashboard/__tests__/variants.test.tsx +95 -0
- package/src/components/dashboard/activity-list.tsx +99 -0
- package/src/components/dashboard/catalog.json +312 -0
- package/src/components/dashboard/catalog.ts +29 -0
- package/src/components/dashboard/data-boundary.tsx +54 -0
- package/src/components/dashboard/empty-state.tsx +70 -0
- package/src/components/dashboard/icon-button.tsx +58 -0
- package/src/components/dashboard/index.ts +21 -0
- package/src/components/dashboard/item-list.tsx +106 -0
- package/src/components/dashboard/key-value.tsx +54 -0
- package/src/components/dashboard/layout.tsx +286 -0
- package/src/components/dashboard/metric.tsx +139 -0
- package/src/components/dashboard/progress-ring.tsx +100 -0
- package/src/components/dashboard/skeletons.tsx +73 -0
- package/src/components/dashboard/spacing.ts +14 -0
- package/src/components/dashboard/stale-indicator.tsx +55 -0
- package/src/components/dashboard/status.tsx +88 -0
- package/src/components/dashboard/tone.ts +48 -0
- package/src/components/dashboard/typography.tsx +151 -0
- package/src/components/reference/ActivityExample.tsx +68 -0
- package/src/components/reference/ResourceExample.tsx +78 -0
- package/src/components/reference/SchedulerExample.tsx +88 -0
- package/src/components/reference/StarterExample.tsx +48 -0
- package/src/components/reference/index.ts +10 -0
- package/src/components/ui/badge.tsx +6 -0
- package/src/index.ts +7 -0
- package/src/reference.ts +15 -0
- package/src/styles/glass-board.css +43 -0
- package/src/styles/globals.css +59 -0
- package/tsup.config.ts +18 -4
- package/tsup.reference.config.ts +15 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { FileText } from 'lucide-react';
|
|
2
|
+
import { ContextSection } from './parts';
|
|
3
|
+
|
|
4
|
+
export interface SystemPromptCopy {
|
|
5
|
+
/** Section title. Defaults to "System Prompt". */
|
|
6
|
+
title?: string;
|
|
7
|
+
/** Help text when no override is set. */
|
|
8
|
+
defaultHint?: string;
|
|
9
|
+
/** Help text when the prompt is set to empty string. */
|
|
10
|
+
emptyHint?: string;
|
|
11
|
+
/** Help text when a custom prompt is set. */
|
|
12
|
+
customHint?: string;
|
|
13
|
+
/** Textarea placeholder. */
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
/** Footer note under the textarea. */
|
|
16
|
+
footnote?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const DEFAULT_COPY: Required<SystemPromptCopy> = {
|
|
20
|
+
title: 'System Prompt',
|
|
21
|
+
defaultHint: 'Using the default system prompt',
|
|
22
|
+
emptyHint: 'System prompt excluded',
|
|
23
|
+
customHint: 'Using a custom system prompt',
|
|
24
|
+
placeholder: 'Enter system prompt...',
|
|
25
|
+
footnote: 'Leave the prompt blank to exclude it entirely, or reset to use the default.',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
interface SystemPromptSectionProps {
|
|
29
|
+
displayedPrompt: string;
|
|
30
|
+
systemPrompt: string | null;
|
|
31
|
+
onSystemPromptChange: (value: string | null) => void;
|
|
32
|
+
copy?: SystemPromptCopy;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function SystemPromptSection({
|
|
36
|
+
displayedPrompt,
|
|
37
|
+
systemPrompt,
|
|
38
|
+
onSystemPromptChange,
|
|
39
|
+
copy,
|
|
40
|
+
}: SystemPromptSectionProps) {
|
|
41
|
+
const c = { ...DEFAULT_COPY, ...copy };
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<ContextSection
|
|
45
|
+
icon={FileText}
|
|
46
|
+
title={c.title}
|
|
47
|
+
tint="blue"
|
|
48
|
+
badge={
|
|
49
|
+
systemPrompt !== null
|
|
50
|
+
? (systemPrompt === '' ? 'excluded' : 'custom')
|
|
51
|
+
: undefined
|
|
52
|
+
}
|
|
53
|
+
badgeVariant={
|
|
54
|
+
systemPrompt !== null
|
|
55
|
+
? (systemPrompt === '' ? 'disabled' : 'modified')
|
|
56
|
+
: 'default'
|
|
57
|
+
}
|
|
58
|
+
defaultOpen={false}
|
|
59
|
+
>
|
|
60
|
+
<div className="space-y-2">
|
|
61
|
+
<div className="flex items-center justify-between">
|
|
62
|
+
<span className="text-[10px] text-[var(--text-muted)]">
|
|
63
|
+
{systemPrompt === null
|
|
64
|
+
? c.defaultHint
|
|
65
|
+
: systemPrompt === ''
|
|
66
|
+
? c.emptyHint
|
|
67
|
+
: c.customHint}
|
|
68
|
+
</span>
|
|
69
|
+
{systemPrompt !== null && (
|
|
70
|
+
<button type="button"
|
|
71
|
+
onClick={() => onSystemPromptChange(null)}
|
|
72
|
+
className="text-[10px] text-[var(--text-muted)] transition-colors hover:text-[var(--text-secondary)]"
|
|
73
|
+
>
|
|
74
|
+
Reset to default
|
|
75
|
+
</button>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
<textarea aria-label="System prompt"
|
|
79
|
+
value={displayedPrompt}
|
|
80
|
+
onChange={(event) => onSystemPromptChange(event.target.value)}
|
|
81
|
+
className="h-80 w-full resize-y rounded-md border border-border/30 bg-[var(--bg-base)] p-2 font-mono text-[11px] text-[var(--text-secondary)] placeholder:text-[var(--text-muted)] outline-none focus:border-[var(--accent-primary)]"
|
|
82
|
+
placeholder={c.placeholder}
|
|
83
|
+
/>
|
|
84
|
+
<p className="text-[10px] text-[var(--text-muted)]">{c.footnote}</p>
|
|
85
|
+
</div>
|
|
86
|
+
</ContextSection>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Wrench } from 'lucide-react';
|
|
2
|
+
import type { ContextToolInfo } from '@sero-ai/common';
|
|
3
|
+
import { CapabilitySection } from './CapabilitySection';
|
|
4
|
+
|
|
5
|
+
interface ToolsSectionProps {
|
|
6
|
+
items: ContextToolInfo[];
|
|
7
|
+
allDisabled: boolean;
|
|
8
|
+
enabledCount: number;
|
|
9
|
+
isEnabled: (name: string) => boolean;
|
|
10
|
+
onToggle: (name: string) => void;
|
|
11
|
+
onToggleAll: (disabled: boolean) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ToolsSection({
|
|
15
|
+
items,
|
|
16
|
+
allDisabled,
|
|
17
|
+
enabledCount,
|
|
18
|
+
isEnabled,
|
|
19
|
+
onToggle,
|
|
20
|
+
onToggleAll,
|
|
21
|
+
}: ToolsSectionProps) {
|
|
22
|
+
return (
|
|
23
|
+
<CapabilitySection
|
|
24
|
+
icon={Wrench}
|
|
25
|
+
title="Tools"
|
|
26
|
+
tint="amber"
|
|
27
|
+
includeAllLabel="Include all tools"
|
|
28
|
+
emptyLabel="No tools available"
|
|
29
|
+
items={items}
|
|
30
|
+
allDisabled={allDisabled}
|
|
31
|
+
enabledCount={enabledCount}
|
|
32
|
+
isEnabled={isEnabled}
|
|
33
|
+
onToggle={onToggle}
|
|
34
|
+
onToggleAll={onToggleAll}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ContextEditor, type ContextEditorProps } from './ContextEditor';
|
|
2
|
+
export {
|
|
3
|
+
useContextEditorModel,
|
|
4
|
+
type ContextEditorModel,
|
|
5
|
+
type UseContextEditorModelParams,
|
|
6
|
+
} from './use-context-editor-model';
|
|
7
|
+
export { type SystemPromptCopy } from './SystemPromptSection';
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentational sub-components for the shared ContextEditor dialog.
|
|
3
|
+
* Controlled, host-agnostic — no window.sero / session coupling.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState } from 'react';
|
|
7
|
+
import { AnimatePresence, motion } from 'motion/react';
|
|
8
|
+
import { ChevronRight } from 'lucide-react';
|
|
9
|
+
import { cn } from '../../lib/utils';
|
|
10
|
+
import { Switch } from '../ui/switch';
|
|
11
|
+
|
|
12
|
+
// ── Color tint definitions ──────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
type SectionTint = 'blue' | 'amber' | 'violet' | 'neutral';
|
|
15
|
+
type BadgeVariant = 'default' | 'modified' | 'disabled' | 'partial';
|
|
16
|
+
|
|
17
|
+
const iconTintClass: Record<SectionTint, string> = {
|
|
18
|
+
blue: 'text-status-info',
|
|
19
|
+
amber: 'text-status-warning',
|
|
20
|
+
violet: 'text-[var(--collab-primary)]',
|
|
21
|
+
neutral: 'text-[var(--text-muted)]',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const badgeClass: Record<BadgeVariant, string> = {
|
|
25
|
+
default: 'bg-[var(--bg-base)] text-[var(--text-muted)]',
|
|
26
|
+
modified: 'bg-status-warning-subtle text-status-warning',
|
|
27
|
+
disabled: 'bg-status-error-subtle text-status-error',
|
|
28
|
+
partial: 'bg-status-info-subtle text-status-info',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const sectionBorderClass: Record<BadgeVariant, string> = {
|
|
32
|
+
default: 'border-border/50',
|
|
33
|
+
modified: 'border-status-warning-border',
|
|
34
|
+
disabled: 'border-status-error-border',
|
|
35
|
+
partial: 'border-status-info-border',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const sectionBgClass: Record<BadgeVariant, string> = {
|
|
39
|
+
default: 'bg-[var(--bg-elevated)]/50',
|
|
40
|
+
modified: 'bg-status-warning-faint',
|
|
41
|
+
disabled: 'bg-status-error-faint',
|
|
42
|
+
partial: 'bg-status-info-faint',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ── Collapsible Section ─────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
export function ContextSection({
|
|
48
|
+
icon: Icon,
|
|
49
|
+
title,
|
|
50
|
+
count,
|
|
51
|
+
badge,
|
|
52
|
+
badgeVariant = 'default',
|
|
53
|
+
tint = 'neutral',
|
|
54
|
+
defaultOpen = false,
|
|
55
|
+
children,
|
|
56
|
+
}: {
|
|
57
|
+
icon: React.ComponentType<{ className?: string }>;
|
|
58
|
+
title: string;
|
|
59
|
+
count?: number;
|
|
60
|
+
badge?: string;
|
|
61
|
+
badgeVariant?: BadgeVariant;
|
|
62
|
+
tint?: SectionTint;
|
|
63
|
+
defaultOpen?: boolean;
|
|
64
|
+
children: React.ReactNode;
|
|
65
|
+
}) {
|
|
66
|
+
const [expanded, setExpanded] = useState(defaultOpen);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
className={cn(
|
|
71
|
+
'overflow-hidden rounded-lg border transition-colors duration-200',
|
|
72
|
+
sectionBorderClass[badgeVariant],
|
|
73
|
+
sectionBgClass[badgeVariant],
|
|
74
|
+
)}
|
|
75
|
+
>
|
|
76
|
+
<button type="button"
|
|
77
|
+
onClick={() => setExpanded((prev) => !prev)}
|
|
78
|
+
className={cn(
|
|
79
|
+
'flex w-full items-center gap-2.5 px-3 py-2 text-left transition-colors duration-150',
|
|
80
|
+
'hover:bg-[var(--bg-elevated)]/80',
|
|
81
|
+
)}
|
|
82
|
+
>
|
|
83
|
+
<motion.div
|
|
84
|
+
animate={{ rotate: expanded ? 90 : 0 }}
|
|
85
|
+
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
|
|
86
|
+
>
|
|
87
|
+
<ChevronRight className="size-3.5 text-[var(--text-muted)]" />
|
|
88
|
+
</motion.div>
|
|
89
|
+
|
|
90
|
+
<Icon className={cn('size-3.5', iconTintClass[tint])} />
|
|
91
|
+
|
|
92
|
+
<span className="text-xs font-medium text-[var(--text-secondary)]">
|
|
93
|
+
{title}
|
|
94
|
+
</span>
|
|
95
|
+
|
|
96
|
+
{count !== undefined && (
|
|
97
|
+
<span className="text-[11px] text-[var(--text-muted)]">
|
|
98
|
+
({count})
|
|
99
|
+
</span>
|
|
100
|
+
)}
|
|
101
|
+
|
|
102
|
+
{badge && (
|
|
103
|
+
<span
|
|
104
|
+
className={cn(
|
|
105
|
+
'ml-auto rounded-full px-2 py-0.5 text-[10px] font-medium',
|
|
106
|
+
badgeClass[badgeVariant],
|
|
107
|
+
)}
|
|
108
|
+
>
|
|
109
|
+
{badge}
|
|
110
|
+
</span>
|
|
111
|
+
)}
|
|
112
|
+
</button>
|
|
113
|
+
|
|
114
|
+
<AnimatePresence>
|
|
115
|
+
{expanded && (
|
|
116
|
+
<motion.div
|
|
117
|
+
initial={{ height: 0, opacity: 0 }}
|
|
118
|
+
animate={{ height: 'auto', opacity: 1 }}
|
|
119
|
+
exit={{ height: 0, opacity: 0 }}
|
|
120
|
+
transition={{ duration: 0.2, ease: [0.25, 0.46, 0.45, 0.94] }}
|
|
121
|
+
className="overflow-hidden"
|
|
122
|
+
>
|
|
123
|
+
<div className="min-w-0 border-t border-border/30 p-3">
|
|
124
|
+
{children}
|
|
125
|
+
</div>
|
|
126
|
+
</motion.div>
|
|
127
|
+
)}
|
|
128
|
+
</AnimatePresence>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── Generic Toggle Row (used for both tools and skills) ─────────
|
|
134
|
+
|
|
135
|
+
export function ToggleRow({
|
|
136
|
+
name,
|
|
137
|
+
description,
|
|
138
|
+
enabled,
|
|
139
|
+
onToggle,
|
|
140
|
+
}: {
|
|
141
|
+
name: string;
|
|
142
|
+
description?: string;
|
|
143
|
+
enabled: boolean;
|
|
144
|
+
onToggle: () => void;
|
|
145
|
+
}) {
|
|
146
|
+
return (
|
|
147
|
+
<div
|
|
148
|
+
className={cn(
|
|
149
|
+
'flex min-w-0 items-start justify-between gap-3 rounded-md px-2 py-1.5 transition-colors',
|
|
150
|
+
enabled
|
|
151
|
+
? 'hover:bg-[var(--bg-elevated)]/60'
|
|
152
|
+
: 'opacity-60 hover:bg-[var(--bg-elevated)]/40',
|
|
153
|
+
)}
|
|
154
|
+
>
|
|
155
|
+
<div className="min-w-0 flex-1">
|
|
156
|
+
<div className={cn(
|
|
157
|
+
'text-[11px] font-medium',
|
|
158
|
+
enabled ? 'text-[var(--text-secondary)]' : 'text-[var(--text-muted)]',
|
|
159
|
+
)}>
|
|
160
|
+
{name}
|
|
161
|
+
</div>
|
|
162
|
+
{description && (
|
|
163
|
+
<div className="mt-0.5 text-[10px] leading-snug text-[var(--text-muted)]/60">
|
|
164
|
+
{description}
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
<Switch
|
|
169
|
+
size="sm"
|
|
170
|
+
className="mt-0.5 shrink-0"
|
|
171
|
+
checked={enabled}
|
|
172
|
+
onCheckedChange={onToggle}
|
|
173
|
+
/>
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── Save Preset Input ───────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
export function SavePresetInput({
|
|
181
|
+
onSave,
|
|
182
|
+
onCancel,
|
|
183
|
+
}: {
|
|
184
|
+
onSave: (name: string) => void;
|
|
185
|
+
onCancel: () => void;
|
|
186
|
+
}) {
|
|
187
|
+
const [name, setName] = useState('');
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<div className="flex items-center gap-2">
|
|
191
|
+
<input aria-label="Preset name"
|
|
192
|
+
type="text"
|
|
193
|
+
value={name}
|
|
194
|
+
onChange={(e) => setName(e.target.value)}
|
|
195
|
+
placeholder="Preset name..."
|
|
196
|
+
className="flex-1 rounded-md border border-border/50 bg-[var(--bg-base)] px-2 py-1 text-xs text-[var(--text-primary)] placeholder:text-[var(--text-muted)] outline-none focus:border-[var(--accent-primary)]"
|
|
197
|
+
onKeyDown={(e) => {
|
|
198
|
+
if (e.key === 'Enter' && name.trim()) onSave(name.trim());
|
|
199
|
+
if (e.key === 'Escape') onCancel();
|
|
200
|
+
}}
|
|
201
|
+
/>
|
|
202
|
+
<button type="button"
|
|
203
|
+
onClick={() => name.trim() && onSave(name.trim())}
|
|
204
|
+
disabled={!name.trim()}
|
|
205
|
+
className="rounded-md bg-[var(--accent-primary)] px-2 py-1 text-[11px] font-medium text-white disabled:opacity-50"
|
|
206
|
+
>
|
|
207
|
+
Save
|
|
208
|
+
</button>
|
|
209
|
+
<button type="button"
|
|
210
|
+
onClick={onCancel}
|
|
211
|
+
className="text-[11px] text-[var(--text-muted)] hover:text-[var(--text-secondary)]"
|
|
212
|
+
>
|
|
213
|
+
Cancel
|
|
214
|
+
</button>
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useContextEditorModel — the controlled editing state for the ContextEditor.
|
|
3
|
+
*
|
|
4
|
+
* Holds the working overrides (system prompt, disabled tools/skills, preset
|
|
5
|
+
* selection) and all the toggle/normalize/preset logic, seeded from the
|
|
6
|
+
* `available` context and `initialOverrides`. Host-agnostic: persistence
|
|
7
|
+
* (apply / save preset / delete preset) is injected by the consumer, so the
|
|
8
|
+
* chat session editor and an app module (e.g. an Orchestrator loop) share one
|
|
9
|
+
* implementation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
13
|
+
import type {
|
|
14
|
+
AvailableContext,
|
|
15
|
+
ContextOverrides,
|
|
16
|
+
ContextPreset,
|
|
17
|
+
ContextSkillInfo,
|
|
18
|
+
ContextToolInfo,
|
|
19
|
+
} from '@sero-ai/common';
|
|
20
|
+
|
|
21
|
+
const NONE_PRESET: ContextPreset = {
|
|
22
|
+
id: '__none__',
|
|
23
|
+
name: 'None',
|
|
24
|
+
systemPrompt: '',
|
|
25
|
+
disabledTools: ['__all__'],
|
|
26
|
+
disabledSkills: ['__all__'],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const DEFAULT_PRESET: ContextPreset = {
|
|
30
|
+
id: '__default__',
|
|
31
|
+
name: 'Default',
|
|
32
|
+
systemPrompt: null,
|
|
33
|
+
disabledTools: [],
|
|
34
|
+
disabledSkills: [],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const BUILTIN_PRESETS: ContextPreset[] = [DEFAULT_PRESET, NONE_PRESET];
|
|
38
|
+
|
|
39
|
+
function normalizeDisabledNames(names: string[] | undefined, availableNames: string[]): Set<string> {
|
|
40
|
+
const allowed = new Set(availableNames);
|
|
41
|
+
return new Set((names ?? []).filter((name) => allowed.has(name)));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function appliedSystemPrompt(overrides: ContextOverrides | null): string | null {
|
|
45
|
+
if (!overrides || !Object.prototype.hasOwnProperty.call(overrides, 'systemPrompt')) return null;
|
|
46
|
+
return typeof overrides.systemPrompt === 'string' ? overrides.systemPrompt : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function enabledCount<TItem extends ContextToolInfo | ContextSkillInfo>(
|
|
50
|
+
items: TItem[],
|
|
51
|
+
disabled: Set<string>,
|
|
52
|
+
allDisabled: boolean,
|
|
53
|
+
): number {
|
|
54
|
+
if (allDisabled) return 0;
|
|
55
|
+
return items.filter((item) => !disabled.has(item.name)).length;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The persistable body of a preset (id assigned by the consumer). */
|
|
59
|
+
export type ContextPresetBody = Pick<ContextPreset, 'systemPrompt' | 'disabledTools' | 'disabledSkills'>;
|
|
60
|
+
|
|
61
|
+
interface CapabilityState<TItem extends ContextToolInfo | ContextSkillInfo> {
|
|
62
|
+
items: TItem[];
|
|
63
|
+
allDisabled: boolean;
|
|
64
|
+
enabledCount: number;
|
|
65
|
+
isEnabled: (name: string) => boolean;
|
|
66
|
+
onToggle: (name: string) => void;
|
|
67
|
+
onToggleAll: (disabled: boolean) => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ContextEditorModel {
|
|
71
|
+
displayedPrompt: string;
|
|
72
|
+
systemPrompt: string | null;
|
|
73
|
+
setSystemPrompt: (value: string | null) => void;
|
|
74
|
+
preset: {
|
|
75
|
+
allPresets: ContextPreset[];
|
|
76
|
+
activePresetId: string | null;
|
|
77
|
+
activeUserPreset: ContextPreset | null;
|
|
78
|
+
hasOverrides: boolean;
|
|
79
|
+
showSaveInput: boolean;
|
|
80
|
+
onPresetChange: (id: string) => void;
|
|
81
|
+
onDelete: (id: string) => void;
|
|
82
|
+
onReset: () => void;
|
|
83
|
+
onShowSave: () => void;
|
|
84
|
+
onSave: (name: string) => void;
|
|
85
|
+
onCancelSave: () => void;
|
|
86
|
+
};
|
|
87
|
+
tools: CapabilityState<ContextToolInfo>;
|
|
88
|
+
skills: CapabilityState<ContextSkillInfo>;
|
|
89
|
+
/** Build the overrides to persist, or null when nothing is overridden. */
|
|
90
|
+
buildOverrides: () => ContextOverrides | null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface UseContextEditorModelParams {
|
|
94
|
+
/** Whether the editor is open — drives one-time seeding per open. */
|
|
95
|
+
open: boolean;
|
|
96
|
+
available: AvailableContext | null;
|
|
97
|
+
initialOverrides: ContextOverrides | null;
|
|
98
|
+
presets: ContextPreset[];
|
|
99
|
+
onSavePreset: (name: string, preset: ContextPresetBody) => void;
|
|
100
|
+
onDeletePreset: (id: string) => void;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function useContextEditorModel({
|
|
104
|
+
open,
|
|
105
|
+
available,
|
|
106
|
+
initialOverrides,
|
|
107
|
+
presets,
|
|
108
|
+
onSavePreset,
|
|
109
|
+
onDeletePreset,
|
|
110
|
+
}: UseContextEditorModelParams): ContextEditorModel {
|
|
111
|
+
const [systemPrompt, setSystemPromptState] = useState<string | null>(null);
|
|
112
|
+
const [disabledTools, setDisabledTools] = useState<Set<string>>(new Set());
|
|
113
|
+
const [disabledSkills, setDisabledSkills] = useState<Set<string>>(new Set());
|
|
114
|
+
const [allToolsDisabled, setAllToolsDisabled] = useState(false);
|
|
115
|
+
const [allSkillsDisabled, setAllSkillsDisabled] = useState(false);
|
|
116
|
+
const [activePresetId, setActivePresetId] = useState<string | null>('__default__');
|
|
117
|
+
const [showSaveInput, setShowSaveInput] = useState(false);
|
|
118
|
+
|
|
119
|
+
const tools = available?.tools ?? [];
|
|
120
|
+
const skills = available?.skills ?? [];
|
|
121
|
+
|
|
122
|
+
// Seed once per open, after the available context has loaded, so the editor
|
|
123
|
+
// reflects the target's current overrides without clobbering live edits.
|
|
124
|
+
const seededRef = useRef(false);
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (!open) {
|
|
127
|
+
seededRef.current = false;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (seededRef.current || !available) return;
|
|
131
|
+
seededRef.current = true;
|
|
132
|
+
|
|
133
|
+
const seedDisabledTools = normalizeDisabledNames(
|
|
134
|
+
initialOverrides?.disabledTools,
|
|
135
|
+
available.tools.map((t) => t.name),
|
|
136
|
+
);
|
|
137
|
+
const seedDisabledSkills = normalizeDisabledNames(
|
|
138
|
+
initialOverrides?.disabledSkills,
|
|
139
|
+
available.skills.map((s) => s.name),
|
|
140
|
+
);
|
|
141
|
+
const allTools = available.tools.length > 0 && seedDisabledTools.size >= available.tools.length;
|
|
142
|
+
const allSkills = available.skills.length > 0 && seedDisabledSkills.size >= available.skills.length;
|
|
143
|
+
|
|
144
|
+
setSystemPromptState(appliedSystemPrompt(initialOverrides));
|
|
145
|
+
setDisabledTools(allTools ? new Set() : seedDisabledTools);
|
|
146
|
+
setDisabledSkills(allSkills ? new Set() : seedDisabledSkills);
|
|
147
|
+
setAllToolsDisabled(allTools);
|
|
148
|
+
setAllSkillsDisabled(allSkills);
|
|
149
|
+
setActivePresetId(initialOverrides ? null : '__default__');
|
|
150
|
+
setShowSaveInput(false);
|
|
151
|
+
}, [open, available, initialOverrides]);
|
|
152
|
+
|
|
153
|
+
const setSystemPrompt = useCallback((value: string | null) => {
|
|
154
|
+
setSystemPromptState(value);
|
|
155
|
+
setActivePresetId(null);
|
|
156
|
+
}, []);
|
|
157
|
+
|
|
158
|
+
const toggleCapability = useCallback(
|
|
159
|
+
(
|
|
160
|
+
name: string,
|
|
161
|
+
items: { name: string }[],
|
|
162
|
+
disabled: Set<string>,
|
|
163
|
+
allDisabled: boolean,
|
|
164
|
+
setDisabled: (s: Set<string>) => void,
|
|
165
|
+
setAll: (b: boolean) => void,
|
|
166
|
+
) => {
|
|
167
|
+
let next: Set<string>;
|
|
168
|
+
if (allDisabled) {
|
|
169
|
+
next = new Set(items.map((i) => i.name));
|
|
170
|
+
next.delete(name);
|
|
171
|
+
} else {
|
|
172
|
+
next = new Set(disabled);
|
|
173
|
+
if (next.has(name)) next.delete(name);
|
|
174
|
+
else next.add(name);
|
|
175
|
+
}
|
|
176
|
+
const total = items.length;
|
|
177
|
+
const allNowDisabled = total > 0 && next.size >= total;
|
|
178
|
+
setDisabled(allNowDisabled ? new Set() : next);
|
|
179
|
+
setAll(allNowDisabled);
|
|
180
|
+
setActivePresetId(null);
|
|
181
|
+
},
|
|
182
|
+
[],
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const loadPreset = useCallback(
|
|
186
|
+
(presetId: string) => {
|
|
187
|
+
const all = [...BUILTIN_PRESETS, ...presets];
|
|
188
|
+
const preset = all.find((p) => p.id === presetId);
|
|
189
|
+
if (!preset) return;
|
|
190
|
+
const allTools = preset.disabledTools.includes('__all__');
|
|
191
|
+
const allSkills = preset.disabledSkills.includes('__all__');
|
|
192
|
+
setSystemPromptState(preset.systemPrompt);
|
|
193
|
+
setDisabledTools(new Set(allTools ? [] : preset.disabledTools));
|
|
194
|
+
setDisabledSkills(new Set(allSkills ? [] : preset.disabledSkills));
|
|
195
|
+
setAllToolsDisabled(allTools);
|
|
196
|
+
setAllSkillsDisabled(allSkills);
|
|
197
|
+
setActivePresetId(presetId);
|
|
198
|
+
},
|
|
199
|
+
[presets],
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const resetToDefault = useCallback(() => {
|
|
203
|
+
setSystemPromptState(null);
|
|
204
|
+
setDisabledTools(new Set());
|
|
205
|
+
setDisabledSkills(new Set());
|
|
206
|
+
setAllToolsDisabled(false);
|
|
207
|
+
setAllSkillsDisabled(false);
|
|
208
|
+
setActivePresetId('__default__');
|
|
209
|
+
setShowSaveInput(false);
|
|
210
|
+
}, []);
|
|
211
|
+
|
|
212
|
+
const buildOverrides = useCallback((): ContextOverrides | null => {
|
|
213
|
+
const toolsToDisable = allToolsDisabled ? tools.map((t) => t.name) : Array.from(disabledTools);
|
|
214
|
+
const skillsToDisable = allSkillsDisabled ? skills.map((s) => s.name) : Array.from(disabledSkills);
|
|
215
|
+
const has = systemPrompt !== null || toolsToDisable.length > 0 || skillsToDisable.length > 0;
|
|
216
|
+
if (!has) return null;
|
|
217
|
+
return {
|
|
218
|
+
systemPrompt: systemPrompt ?? undefined,
|
|
219
|
+
disabledTools: toolsToDisable.length > 0 ? toolsToDisable : undefined,
|
|
220
|
+
disabledSkills: skillsToDisable.length > 0 ? skillsToDisable : undefined,
|
|
221
|
+
};
|
|
222
|
+
}, [allToolsDisabled, allSkillsDisabled, tools, skills, disabledTools, disabledSkills, systemPrompt]);
|
|
223
|
+
|
|
224
|
+
const handleSavePreset = useCallback(
|
|
225
|
+
(name: string) => {
|
|
226
|
+
onSavePreset(name, {
|
|
227
|
+
systemPrompt,
|
|
228
|
+
disabledTools: allToolsDisabled ? ['__all__'] : Array.from(disabledTools),
|
|
229
|
+
disabledSkills: allSkillsDisabled ? ['__all__'] : Array.from(disabledSkills),
|
|
230
|
+
});
|
|
231
|
+
setShowSaveInput(false);
|
|
232
|
+
},
|
|
233
|
+
[onSavePreset, systemPrompt, allToolsDisabled, disabledTools, allSkillsDisabled, disabledSkills],
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const allPresets = useMemo(() => [...BUILTIN_PRESETS, ...presets], [presets]);
|
|
237
|
+
const activeUserPreset = useMemo(
|
|
238
|
+
() =>
|
|
239
|
+
activePresetId && !activePresetId.startsWith('__')
|
|
240
|
+
? allPresets.find((p) => p.id === activePresetId) ?? null
|
|
241
|
+
: null,
|
|
242
|
+
[activePresetId, allPresets],
|
|
243
|
+
);
|
|
244
|
+
const hasOverrides =
|
|
245
|
+
systemPrompt !== null ||
|
|
246
|
+
disabledTools.size > 0 ||
|
|
247
|
+
disabledSkills.size > 0 ||
|
|
248
|
+
allToolsDisabled ||
|
|
249
|
+
allSkillsDisabled;
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
displayedPrompt: systemPrompt ?? available?.systemPrompt ?? '',
|
|
253
|
+
systemPrompt,
|
|
254
|
+
setSystemPrompt,
|
|
255
|
+
preset: {
|
|
256
|
+
allPresets,
|
|
257
|
+
activePresetId,
|
|
258
|
+
activeUserPreset,
|
|
259
|
+
hasOverrides,
|
|
260
|
+
showSaveInput,
|
|
261
|
+
onPresetChange: loadPreset,
|
|
262
|
+
onDelete: onDeletePreset,
|
|
263
|
+
onReset: resetToDefault,
|
|
264
|
+
onShowSave: () => setShowSaveInput(true),
|
|
265
|
+
onSave: handleSavePreset,
|
|
266
|
+
onCancelSave: () => setShowSaveInput(false),
|
|
267
|
+
},
|
|
268
|
+
tools: {
|
|
269
|
+
items: tools,
|
|
270
|
+
allDisabled: allToolsDisabled,
|
|
271
|
+
enabledCount: enabledCount(tools, disabledTools, allToolsDisabled),
|
|
272
|
+
isEnabled: (name) => !allToolsDisabled && !disabledTools.has(name),
|
|
273
|
+
onToggle: (name) =>
|
|
274
|
+
toggleCapability(name, tools, disabledTools, allToolsDisabled, setDisabledTools, setAllToolsDisabled),
|
|
275
|
+
onToggleAll: (disabled) => {
|
|
276
|
+
setAllToolsDisabled(disabled);
|
|
277
|
+
setDisabledTools(new Set());
|
|
278
|
+
setActivePresetId(null);
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
skills: {
|
|
282
|
+
items: skills,
|
|
283
|
+
allDisabled: allSkillsDisabled,
|
|
284
|
+
enabledCount: enabledCount(skills, disabledSkills, allSkillsDisabled),
|
|
285
|
+
isEnabled: (name) => !allSkillsDisabled && !disabledSkills.has(name),
|
|
286
|
+
onToggle: (name) =>
|
|
287
|
+
toggleCapability(name, skills, disabledSkills, allSkillsDisabled, setDisabledSkills, setAllSkillsDisabled),
|
|
288
|
+
onToggleAll: (disabled) => {
|
|
289
|
+
setAllSkillsDisabled(disabled);
|
|
290
|
+
setDisabledSkills(new Set());
|
|
291
|
+
setActivePresetId(null);
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
buildOverrides,
|
|
295
|
+
};
|
|
296
|
+
}
|