@drawnagency/primitives 0.1.84 → 0.1.87
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/dist/{chunk-665DW3HR.js → chunk-6VOIZP2O.js} +3 -3
- package/dist/{chunk-YKOLOZB2.js → chunk-DRCPDOW3.js} +14 -0
- package/dist/{chunk-MBJUWOM2.js → chunk-EO7K45GV.js} +1 -1
- package/dist/{chunk-DKXISNJ2.js → chunk-NEDOJHHR.js} +2 -2
- package/dist/{chunk-DO3ZENLN.js → chunk-OEL57NYV.js} +1 -1
- package/dist/{chunk-FDBZZ4AT.js → chunk-PTVY3Q7P.js} +1 -1
- package/dist/{chunk-56J4QMF4.js → chunk-VY5V4HBV.js} +7 -7
- package/dist/{chunk-EP3LVOXZ.js → chunk-WA3HFN54.js} +1 -1
- package/dist/{chunk-Q6LZOH5M.js → chunk-XDG2K6UT.js} +2 -2
- package/dist/{chunk-E3MTIMOG.js → chunk-XPUBUR64.js} +10 -0
- package/dist/collab/rebase.d.ts +23 -0
- package/dist/collab/rebase.d.ts.map +1 -1
- package/dist/collab/rebase.js +12 -1
- package/dist/components/editor/index.js +7 -7
- package/dist/components/primitives/index.js +5 -5
- package/dist/components/sections/brand-guide.js +5 -5
- package/dist/components/sections/register-schemas.js +8 -8
- package/dist/components/sections/register.js +8 -8
- package/dist/components/shared/Navigation.d.ts +6 -1
- package/dist/components/shared/Navigation.d.ts.map +1 -1
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/components/shell/{ChatbotConfigModal.d.ts → SiteSettingsChatbot.d.ts} +2 -4
- package/dist/components/shell/SiteSettingsChatbot.d.ts.map +1 -0
- package/dist/components/shell/SiteSettingsDisplay.d.ts.map +1 -1
- package/dist/components/shell/SiteSettingsModal.d.ts +3 -1
- package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -1
- package/dist/hooks/useMediaPipeline.d.ts +2 -1
- package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/lib/image-refs.d.ts +9 -0
- package/dist/lib/image-refs.d.ts.map +1 -1
- package/dist/lib/index.js +2 -2
- package/dist/media/index.js +4 -2
- package/dist/media/resolve.d.ts +25 -0
- package/dist/media/resolve.d.ts.map +1 -1
- package/dist/schemas/index.js +2 -2
- package/dist/schemas/site-config.d.ts +10 -0
- package/dist/schemas/site-config.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/collab/rebase.ts +33 -0
- package/src/components/editor/ChildBlockWrapper.tsx +1 -1
- package/src/components/editor/PagesModal.tsx +39 -31
- package/src/components/editor/SectionWrapper.tsx +3 -3
- package/src/components/shared/Navigation.tsx +20 -2
- package/src/components/shared/Tabs.tsx +1 -1
- package/src/components/shell/EditorShell.tsx +29 -76
- package/src/components/shell/SiteSettingsChatbot.tsx +188 -0
- package/src/components/shell/SiteSettingsDisplay.tsx +106 -0
- package/src/components/shell/SiteSettingsModal.tsx +29 -11
- package/src/hooks/useMediaPipeline.ts +16 -3
- package/src/lib/image-refs.ts +19 -0
- package/src/media/resolve.ts +34 -0
- package/src/schemas/site-config.ts +13 -0
- package/dist/components/shell/ChatbotConfigModal.d.ts.map +0 -1
- package/src/components/shell/ChatbotConfigModal.tsx +0 -193
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { EditorModal } from "./EditorModal";
|
|
3
|
-
import { Button } from "../shared/Button";
|
|
4
|
-
import { cn } from "../../lib/cn";
|
|
5
|
-
import ChatPanel from "../chat/ChatPanel";
|
|
6
|
-
import type { SiteConfig, ChatbotConfig, ChatbotTask } from "../../schemas/site-config";
|
|
7
|
-
import type { Audience } from "../../auth/types";
|
|
8
|
-
import { audiencePasses } from "../../lib/pages";
|
|
9
|
-
|
|
10
|
-
interface Props {
|
|
11
|
-
isOpen: boolean;
|
|
12
|
-
onClose: () => void;
|
|
13
|
-
siteConfig: SiteConfig;
|
|
14
|
-
onSiteConfigChange: (config: SiteConfig) => void;
|
|
15
|
-
audiences: Audience[];
|
|
16
|
-
platformEnabled: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function slugify(label: string): string {
|
|
20
|
-
return label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "task";
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function AudienceChips({ audiences, selected, onToggle }: { audiences: Audience[]; selected: string[]; onToggle: (name: string) => void }) {
|
|
24
|
-
return (
|
|
25
|
-
<div className="flex flex-wrap gap-1">
|
|
26
|
-
{audiences.map((a) => (
|
|
27
|
-
<button key={a.name} type="button" onClick={() => onToggle(a.name)}
|
|
28
|
-
className={cn("rounded-full border px-2 py-0.5 text-xs",
|
|
29
|
-
selected.includes(a.name) ? "border-primary bg-primary text-primary-contrast" : "border-base-200 bg-base-accent text-base-contrast")}
|
|
30
|
-
style={a.color && selected.includes(a.name) ? { backgroundColor: a.color, borderColor: a.color } : undefined}>
|
|
31
|
-
{a.displayName}
|
|
32
|
-
</button>
|
|
33
|
-
))}
|
|
34
|
-
{audiences.length === 0 && <span className="text-xs text-base-contrast-light">No audiences configured</span>}
|
|
35
|
-
</div>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function ChatbotConfigModal({ isOpen, onClose, siteConfig, onSiteConfigChange, audiences, platformEnabled }: Props) {
|
|
40
|
-
const chatbot = siteConfig.chatbot;
|
|
41
|
-
const [newTaskLabel, setNewTaskLabel] = useState("");
|
|
42
|
-
const [showTest, setShowTest] = useState(false);
|
|
43
|
-
const [previewAudience, setPreviewAudience] = useState("");
|
|
44
|
-
|
|
45
|
-
// A deselected audience can no longer chat — never keep simulating it.
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
if (previewAudience && !chatbot.audiences.includes(previewAudience)) setPreviewAudience("");
|
|
48
|
-
}, [previewAudience, chatbot.audiences]);
|
|
49
|
-
|
|
50
|
-
const patch = (p: Partial<ChatbotConfig>) =>
|
|
51
|
-
onSiteConfigChange({ ...siteConfig, chatbot: { ...chatbot, ...p } });
|
|
52
|
-
|
|
53
|
-
const patchTask = (i: number, p: Partial<ChatbotTask>) =>
|
|
54
|
-
patch({ tasks: chatbot.tasks.map((t, idx) => (idx === i ? { ...t, ...p } : t)) });
|
|
55
|
-
|
|
56
|
-
function addTask() {
|
|
57
|
-
const label = newTaskLabel.trim();
|
|
58
|
-
if (!label || chatbot.tasks.length >= 20) return;
|
|
59
|
-
let id = slugify(label);
|
|
60
|
-
if (chatbot.tasks.some((t) => t.id === id)) {
|
|
61
|
-
for (let n = 2; ; n++) {
|
|
62
|
-
const suffix = `-${n}`;
|
|
63
|
-
const candidate = `${id.slice(0, 32 - suffix.length)}${suffix}`;
|
|
64
|
-
if (!chatbot.tasks.some((t) => t.id === candidate)) {
|
|
65
|
-
id = candidate;
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
patch({ tasks: [...chatbot.tasks, { id, label, prompt: "", access: [] }] });
|
|
71
|
-
setNewTaskLabel("");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const toggleName = (list: string[], name: string) =>
|
|
75
|
-
list.includes(name) ? list.filter((n) => n !== name) : [...list, name];
|
|
76
|
-
|
|
77
|
-
// Record lookups can hit inherited Object.prototype members ("constructor"
|
|
78
|
-
// is a legal audience name) — treat anything non-string as absent.
|
|
79
|
-
const overlayValue = (name: string) => {
|
|
80
|
-
const v = chatbot.audiencePrompts[name];
|
|
81
|
-
return typeof v === "string" ? v : "";
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<EditorModal isOpen={isOpen} onClose={onClose} title="Chatbot" size="settings" noPadding>
|
|
86
|
-
<div className="flex flex-1 flex-col gap-4 overflow-y-auto px-6 py-4">
|
|
87
|
-
{!platformEnabled && (
|
|
88
|
-
<div className="rounded border border-base-200 bg-base-accent px-3 py-2 text-sm text-base-contrast">
|
|
89
|
-
Chat is not enabled for this site — contact Drawn Agency. You can still edit the
|
|
90
|
-
configuration below; it activates when chat is switched on.
|
|
91
|
-
</div>
|
|
92
|
-
)}
|
|
93
|
-
|
|
94
|
-
<label className="flex items-center gap-2 text-sm text-base-contrast">
|
|
95
|
-
<input type="checkbox" aria-label="Enable chatbot" checked={chatbot.enabled}
|
|
96
|
-
className="h-5 w-5 cursor-pointer accent-primary"
|
|
97
|
-
onChange={(e) => patch({ enabled: e.target.checked })} />
|
|
98
|
-
Show the chat widget to viewers
|
|
99
|
-
</label>
|
|
100
|
-
|
|
101
|
-
<div>
|
|
102
|
-
<p className="mb-1 text-sm font-medium text-base-contrast">Audiences</p>
|
|
103
|
-
<p className="mb-2 text-xs text-base-contrast-light">Who sees the widget — only the selected audiences; none selected means no one.</p>
|
|
104
|
-
<AudienceChips audiences={audiences} selected={chatbot.audiences} onToggle={(n) => patch({ audiences: toggleName(chatbot.audiences, n) })} />
|
|
105
|
-
{chatbot.audiences.map((name) => (
|
|
106
|
-
<div key={name} className="mt-2">
|
|
107
|
-
<label htmlFor={`audience-prompt-${name}`} className="mb-1 block text-xs font-medium text-base-contrast">
|
|
108
|
-
Additional guidance for {name}
|
|
109
|
-
</label>
|
|
110
|
-
<textarea id={`audience-prompt-${name}`} aria-label={`Guidance for ${name}`} rows={2} maxLength={8000}
|
|
111
|
-
value={overlayValue(name)}
|
|
112
|
-
onChange={(e) => patch({ audiencePrompts: { ...chatbot.audiencePrompts, [name]: e.target.value } })}
|
|
113
|
-
placeholder={`Only ${name} sees answers shaped by this — safe for ${name}-level info.`}
|
|
114
|
-
className="w-full rounded border border-base-200 bg-base px-2 py-1 text-sm text-base-contrast" />
|
|
115
|
-
</div>
|
|
116
|
-
))}
|
|
117
|
-
</div>
|
|
118
|
-
|
|
119
|
-
<div>
|
|
120
|
-
<label htmlFor="chatbot-system-prompt" className="mb-1 block text-sm font-medium text-base-contrast">System prompt</label>
|
|
121
|
-
<textarea id="chatbot-system-prompt" aria-label="System prompt" rows={5} maxLength={8000}
|
|
122
|
-
value={chatbot.systemPrompt}
|
|
123
|
-
onChange={(e) => patch({ systemPrompt: e.target.value })}
|
|
124
|
-
placeholder="Describe the brand's voice and how the assistant should behave — e.g. “Speak warmly and concisely; always reference our color names, never hex codes.”"
|
|
125
|
-
className="w-full rounded border border-base-200 bg-base px-2 py-1 text-sm text-base-contrast" />
|
|
126
|
-
<p className="text-right text-xs text-base-contrast-light">{chatbot.systemPrompt.length}/8000</p>
|
|
127
|
-
</div>
|
|
128
|
-
|
|
129
|
-
<div>
|
|
130
|
-
<p className="mb-1 text-sm font-medium text-base-contrast">Tasks</p>
|
|
131
|
-
<p className="mb-2 text-xs text-base-contrast-light">Goal presets shown as a dropdown in the widget. Each task layers its own instructions onto the system prompt.</p>
|
|
132
|
-
<div className="space-y-3">
|
|
133
|
-
{chatbot.tasks.map((t, i) => (
|
|
134
|
-
<div key={t.id} className="rounded border border-base-200 p-2">
|
|
135
|
-
<div className="mb-1 flex items-center justify-between gap-2">
|
|
136
|
-
<input aria-label={`Task ${t.id} label`} value={t.label} maxLength={60}
|
|
137
|
-
onChange={(e) => patchTask(i, { label: e.target.value })}
|
|
138
|
-
className="min-w-0 flex-1 rounded border border-base-200 bg-base px-2 py-1 text-sm" />
|
|
139
|
-
<Button type="button" variant="destructive"
|
|
140
|
-
onClick={() => patch({ tasks: chatbot.tasks.filter((_, idx) => idx !== i) })}>
|
|
141
|
-
Remove
|
|
142
|
-
</Button>
|
|
143
|
-
</div>
|
|
144
|
-
<textarea aria-label={`Task ${t.id} prompt`} rows={2} maxLength={4000} value={t.prompt}
|
|
145
|
-
onChange={(e) => patchTask(i, { prompt: e.target.value })}
|
|
146
|
-
placeholder="Instructions for this task…"
|
|
147
|
-
className="mb-1 w-full rounded border border-base-200 bg-base px-2 py-1 text-sm" />
|
|
148
|
-
<AudienceChips audiences={audiences} selected={t.access} onToggle={(n) => patchTask(i, { access: toggleName(t.access, n) })} />
|
|
149
|
-
</div>
|
|
150
|
-
))}
|
|
151
|
-
</div>
|
|
152
|
-
<div className="mt-2 flex gap-2">
|
|
153
|
-
<input aria-label="New task label" value={newTaskLabel} maxLength={60}
|
|
154
|
-
onChange={(e) => setNewTaskLabel(e.target.value)}
|
|
155
|
-
placeholder="e.g. Draft a social post"
|
|
156
|
-
className="min-w-0 flex-1 rounded border border-base-200 bg-base px-2 py-1 text-sm" />
|
|
157
|
-
<Button type="button" onClick={addTask}>Add task</Button>
|
|
158
|
-
</div>
|
|
159
|
-
</div>
|
|
160
|
-
|
|
161
|
-
<div>
|
|
162
|
-
<Button type="button" onClick={() => setShowTest((s) => !s)} disabled={!platformEnabled}>
|
|
163
|
-
{showTest ? "Hide test chat" : "Test with current draft"}
|
|
164
|
-
</Button>
|
|
165
|
-
<p className="mt-1 text-xs text-base-contrast-light">
|
|
166
|
-
Tests your unsaved prompt and tasks. Answers ground on published content.
|
|
167
|
-
</p>
|
|
168
|
-
{showTest && platformEnabled && (
|
|
169
|
-
<label className="mt-2 flex items-center gap-2 text-xs text-base-contrast">
|
|
170
|
-
Preview as
|
|
171
|
-
<select aria-label="Preview as" value={previewAudience} onChange={(e) => setPreviewAudience(e.target.value)}
|
|
172
|
-
className="rounded border border-base-200 bg-base-accent px-1 py-0.5 text-xs">
|
|
173
|
-
<option value="">Editor (base prompt only)</option>
|
|
174
|
-
{chatbot.audiences.map((n) => <option key={n} value={n}>{n}</option>)}
|
|
175
|
-
</select>
|
|
176
|
-
<span className="text-base-contrast-light">Prompt and task preview only — grounding stays editor-wide.</span>
|
|
177
|
-
</label>
|
|
178
|
-
)}
|
|
179
|
-
{showTest && platformEnabled && (
|
|
180
|
-
<ChatPanel
|
|
181
|
-
open onClose={() => setShowTest(false)} siteName={siteConfig.siteName}
|
|
182
|
-
tasks={chatbot.tasks
|
|
183
|
-
.filter((t) => previewAudience === "" || audiencePasses(t.access, previewAudience))
|
|
184
|
-
.map((t) => ({ id: t.id, label: t.label }))}
|
|
185
|
-
isEditor draftChatbot={chatbot}
|
|
186
|
-
simulateAudience={previewAudience || undefined}
|
|
187
|
-
/>
|
|
188
|
-
)}
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
</EditorModal>
|
|
192
|
-
);
|
|
193
|
-
}
|