@drawnagency/primitives 0.1.67 → 0.1.69
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-4YEOTFVU.js +14 -0
- package/dist/{chunk-VU3XR362.js → chunk-6UDYJQL6.js} +1 -1
- package/dist/{chunk-7IMQVDDJ.js → chunk-EWKCEPMW.js} +1 -1
- package/dist/{chunk-XNGG5AGI.js → chunk-KWBM27UR.js} +1 -1
- package/dist/{chunk-TEYTLQVH.js → chunk-QKOIEWZ4.js} +33 -1
- package/dist/{chunk-4VMAZZXT.js → chunk-TYYCKVAW.js} +4 -4
- package/dist/{chunk-KM6HOW4H.js → chunk-VVK6ZRCN.js} +2 -2
- package/dist/{chunk-AFK45HX2.js → chunk-WNPMBFD6.js} +1 -1
- package/dist/{chunk-WJ7RZ6CC.js → chunk-WXVKX6PY.js} +1 -1
- package/dist/components/chat/ChatPanel.d.ts +33 -0
- package/dist/components/chat/ChatPanel.d.ts.map +1 -0
- package/dist/components/chat/ChatWidget.d.ts +14 -0
- package/dist/components/chat/ChatWidget.d.ts.map +1 -0
- package/dist/components/primitives/index.js +5 -5
- package/dist/components/sections/brand-guide.js +5 -5
- package/dist/components/sections/register-schemas.js +7 -7
- package/dist/components/sections/register.js +7 -7
- package/dist/components/shell/ChatbotConfigModal.d.ts +13 -0
- package/dist/components/shell/ChatbotConfigModal.d.ts.map +1 -0
- package/dist/components/shell/EditorShell.d.ts +2 -1
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/index.js +12 -5
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +6 -3
- package/dist/lib/platform-broker.d.ts +7 -0
- package/dist/lib/platform-broker.d.ts.map +1 -1
- package/dist/lib/platform-broker.js +27 -1
- package/dist/media/index.js +3 -3
- package/dist/schemas/index.js +6 -2
- package/dist/schemas/site-config.d.ts +30 -0
- package/dist/schemas/site-config.d.ts.map +1 -1
- package/dist/types/database.d.ts +48 -0
- package/dist/types/database.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/chat/ChatPanel.tsx +158 -0
- package/src/components/chat/ChatWidget.tsx +61 -0
- package/src/components/shell/ChatbotConfigModal.tsx +152 -0
- package/src/components/shell/EditorShell.tsx +43 -1
- package/src/lib/index.ts +1 -0
- package/src/lib/platform-broker.ts +36 -0
- package/src/schemas/site-config.ts +36 -0
- package/src/types/database.ts +48 -0
- package/dist/chunk-ZGFAYZWB.js +0 -8
|
@@ -19,6 +19,8 @@ import type { BlockMove } from "../../lib/block-move";
|
|
|
19
19
|
import { EditorModalProvider, useEditorModal } from "./EditorModalContext";
|
|
20
20
|
import { EditorModal } from "./EditorModal";
|
|
21
21
|
import { SiteSettingsModal } from "./SiteSettingsModal";
|
|
22
|
+
import { ChatbotConfigModal } from "./ChatbotConfigModal";
|
|
23
|
+
import ChatWidget from "../chat/ChatWidget";
|
|
22
24
|
import { MediaLibraryModal } from "./MediaLibraryModal";
|
|
23
25
|
import { MediaLibraryContext } from "./MediaLibraryContext";
|
|
24
26
|
import { ProcessingIndicator } from "./ProcessingIndicator";
|
|
@@ -72,7 +74,7 @@ import { IconButton } from "../shared/IconButton";
|
|
|
72
74
|
import { SegmentedControl } from "../shared/SegmentedControl";
|
|
73
75
|
import { computeDisplaySections } from "./view-branch";
|
|
74
76
|
import { SettingsIcon } from "../shared/icons";
|
|
75
|
-
import { ImageIcon, ListOrderedIcon, FilesIcon, X } from "lucide-react";
|
|
77
|
+
import { ImageIcon, ListOrderedIcon, FilesIcon, MessageCircleIcon, X } from "lucide-react";
|
|
76
78
|
import { ErrorBoundary } from "../shared/ErrorBoundary";
|
|
77
79
|
import { HistoryToolbar } from "./HistoryToolbar";
|
|
78
80
|
import { RestoreModal } from "./RestoreModal";
|
|
@@ -104,6 +106,10 @@ interface Props {
|
|
|
104
106
|
passwordToggle: boolean;
|
|
105
107
|
};
|
|
106
108
|
currentUser: { email: string; role: "owner" | "editor" } | null;
|
|
109
|
+
// Whether the platform (admin) has enabled the chatbot for this site.
|
|
110
|
+
// Controls the configurator's banner + test panel; the config UI itself
|
|
111
|
+
// always works so editors can prepare before activation.
|
|
112
|
+
chatbotPlatformEnabled?: boolean;
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
export default function EditorShell({
|
|
@@ -113,6 +119,7 @@ export default function EditorShell({
|
|
|
113
119
|
audiences: initialAudiences,
|
|
114
120
|
capabilities,
|
|
115
121
|
currentUser,
|
|
122
|
+
chatbotPlatformEnabled = false,
|
|
116
123
|
}: Props) {
|
|
117
124
|
const [shellState, setShellState] = useState<ShellState>({ phase: "loading-content" });
|
|
118
125
|
const [sections, setSections] = useState<LoadedSection[]>([]);
|
|
@@ -143,6 +150,7 @@ export default function EditorShell({
|
|
|
143
150
|
const [showRestoreModal, setShowRestoreModal] = useState(false);
|
|
144
151
|
const [showOrderingModal, setShowOrderingModal] = useState(false);
|
|
145
152
|
const [showPagesModal, setShowPagesModal] = useState(false);
|
|
153
|
+
const [showChatbotConfig, setShowChatbotConfig] = useState(false);
|
|
146
154
|
const [isRestoring, setIsRestoring] = useState(false);
|
|
147
155
|
const [rejectedUploads, setRejectedUploads] = useState<{ name: string; reason: "size" | "type" }[]>([]);
|
|
148
156
|
const [activePageId, setActivePageId] = useState<string | null>(null);
|
|
@@ -887,6 +895,7 @@ export default function EditorShell({
|
|
|
887
895
|
onRestoreClick={() => setShowRestoreModal(true)}
|
|
888
896
|
onOrderingClick={() => setShowOrderingModal(true)}
|
|
889
897
|
onPagesClick={() => setShowPagesModal(true)}
|
|
898
|
+
onChatbotClick={() => setShowChatbotConfig(true)}
|
|
890
899
|
/>
|
|
891
900
|
|
|
892
901
|
{/* Bug reports persist via Supabase. On the password-only (zero-Supabase)
|
|
@@ -894,6 +903,20 @@ export default function EditorShell({
|
|
|
894
903
|
the button would always 401 — hide it there. */}
|
|
895
904
|
{!capabilities.passwordOnly && <BugReportFAB />}
|
|
896
905
|
|
|
906
|
+
{/* Editor chat FAB: chat while editing, against the DRAFT chatbot
|
|
907
|
+
config (tasks + prompt reflect unsaved changes; grounding is
|
|
908
|
+
published content). Gated only on the platform switch — editors
|
|
909
|
+
may chat before the widget is enabled for viewers. */}
|
|
910
|
+
{chatbotPlatformEnabled && siteConfig && (
|
|
911
|
+
<ChatWidget
|
|
912
|
+
siteName={siteConfig.siteName}
|
|
913
|
+
tasks={siteConfig.chatbot.tasks.map((t) => ({ id: t.id, label: t.label }))}
|
|
914
|
+
isEditor
|
|
915
|
+
draftChatbot={siteConfig.chatbot}
|
|
916
|
+
variant="editor"
|
|
917
|
+
/>
|
|
918
|
+
)}
|
|
919
|
+
|
|
897
920
|
{rejectedUploads.length > 0 && (
|
|
898
921
|
<div className="sticky top-2 z-30 mx-auto w-full max-w-3xl px-4">
|
|
899
922
|
<UploadRejectionAlert
|
|
@@ -937,6 +960,16 @@ export default function EditorShell({
|
|
|
937
960
|
capabilities={capabilities}
|
|
938
961
|
currentUser={currentUser}
|
|
939
962
|
/>
|
|
963
|
+
{siteConfig && (
|
|
964
|
+
<ChatbotConfigModal
|
|
965
|
+
isOpen={showChatbotConfig}
|
|
966
|
+
onClose={() => setShowChatbotConfig(false)}
|
|
967
|
+
siteConfig={siteConfig}
|
|
968
|
+
onSiteConfigChange={handleSiteConfigChange}
|
|
969
|
+
audiences={audiences}
|
|
970
|
+
platformEnabled={chatbotPlatformEnabled}
|
|
971
|
+
/>
|
|
972
|
+
)}
|
|
940
973
|
<EditorModal
|
|
941
974
|
isOpen={pendingDeleteSectionId !== null}
|
|
942
975
|
onClose={() => setPendingDeleteSectionId(null)}
|
|
@@ -1522,6 +1555,7 @@ function EditorToolbar({
|
|
|
1522
1555
|
onRestoreClick,
|
|
1523
1556
|
onOrderingClick,
|
|
1524
1557
|
onPagesClick,
|
|
1558
|
+
onChatbotClick,
|
|
1525
1559
|
}: {
|
|
1526
1560
|
buttonState: "synced" | "publish" | "saveAndPublish";
|
|
1527
1561
|
localChangesExist: boolean;
|
|
@@ -1540,6 +1574,7 @@ function EditorToolbar({
|
|
|
1540
1574
|
onRestoreClick: () => void;
|
|
1541
1575
|
onOrderingClick: () => void;
|
|
1542
1576
|
onPagesClick: () => void;
|
|
1577
|
+
onChatbotClick: () => void;
|
|
1543
1578
|
}) {
|
|
1544
1579
|
const { isEditMode, viewBranch, setViewBranch, toggleEditMode, historyState, setHistoryState } = useEditorContext();
|
|
1545
1580
|
|
|
@@ -1625,6 +1660,13 @@ function EditorToolbar({
|
|
|
1625
1660
|
onClick={onPagesClick}
|
|
1626
1661
|
className="border border-base-200 bg-base-accent"
|
|
1627
1662
|
/>
|
|
1663
|
+
<IconButton
|
|
1664
|
+
icon={<MessageCircleIcon size={16} />}
|
|
1665
|
+
label="Chatbot"
|
|
1666
|
+
size="md"
|
|
1667
|
+
onClick={onChatbotClick}
|
|
1668
|
+
className="border border-base-200 bg-base-accent"
|
|
1669
|
+
/>
|
|
1628
1670
|
<IconButton
|
|
1629
1671
|
icon={<SettingsIcon size={16} />}
|
|
1630
1672
|
label="Site settings"
|
package/src/lib/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { env } from "./env";
|
|
2
2
|
export { cn } from "./cn";
|
|
3
|
+
export { audiencePasses } from "./pages";
|
|
3
4
|
export { generateNavLinks, toSectionId, type NavItem } from "./nav";
|
|
4
5
|
export { deriveContrast } from "./contrast";
|
|
5
6
|
export { sanitizeHtml, ensureSanitizer } from "./sanitize";
|
|
@@ -70,3 +70,39 @@ export async function platformBroker<T = unknown>(
|
|
|
70
70
|
|
|
71
71
|
throw lastError instanceof Error ? lastError : new Error("Platform broker unreachable");
|
|
72
72
|
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Streaming variant of platformBroker for SSE endpoints (the LLM broker).
|
|
76
|
+
* Same auth headers; retries only pre-stream connection failures — once bytes
|
|
77
|
+
* are flowing there is no safe retry. Non-2xx throws with the status ONLY
|
|
78
|
+
* (never the body: stream endpoints may proxy provider errors).
|
|
79
|
+
*/
|
|
80
|
+
export async function platformBrokerStream(
|
|
81
|
+
path: string,
|
|
82
|
+
body: unknown,
|
|
83
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
84
|
+
const apiUrl = env("PLATFORM_API_URL");
|
|
85
|
+
const apiKey = env("PLATFORM_API_KEY");
|
|
86
|
+
const siteId = env("PORTAL_SITE_ID");
|
|
87
|
+
const url = `${apiUrl}${path}`;
|
|
88
|
+
|
|
89
|
+
let lastError: unknown;
|
|
90
|
+
for (let attempt = 0; attempt < ATTEMPTS; attempt++) {
|
|
91
|
+
let res: Response;
|
|
92
|
+
try {
|
|
93
|
+
res = await fetch(url, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: { "x-api-key": apiKey, "x-site-id": siteId, "Content-Type": "application/json" },
|
|
96
|
+
body: JSON.stringify(body),
|
|
97
|
+
});
|
|
98
|
+
} catch (err) {
|
|
99
|
+
lastError = err;
|
|
100
|
+
if (attempt === ATTEMPTS - 1) throw err;
|
|
101
|
+
await sleep(RETRY_BASE_MS * 2 ** attempt);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (!res.ok || !res.body) throw new Error(`Platform broker error (${res.status})`);
|
|
105
|
+
return res.body;
|
|
106
|
+
}
|
|
107
|
+
throw lastError instanceof Error ? lastError : new Error("Platform broker unreachable");
|
|
108
|
+
}
|
|
@@ -132,6 +132,38 @@ export function normalizeSiteIndex(raw: unknown): SiteIndex {
|
|
|
132
132
|
return IndexSchema.parse(raw);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
// Task ids share the audience-name slug rules (lowercase alnum/dash/underscore).
|
|
136
|
+
const ChatbotTaskIdSchema = z.string().min(1).max(32).regex(/^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?$/,
|
|
137
|
+
"Task id must be lowercase letters, numbers, dashes, or underscores");
|
|
138
|
+
|
|
139
|
+
export const ChatbotTaskSchema = z.object({
|
|
140
|
+
id: ChatbotTaskIdSchema,
|
|
141
|
+
label: z.string().min(1).max(60),
|
|
142
|
+
prompt: z.string().max(4_000),
|
|
143
|
+
// Audience names allowed to see this task; [] = all — same semantics as SectionMeta.access.
|
|
144
|
+
access: z.array(z.string()).default([]),
|
|
145
|
+
});
|
|
146
|
+
export type ChatbotTask = z.infer<typeof ChatbotTaskSchema>;
|
|
147
|
+
|
|
148
|
+
export const ChatbotConfigSchema = z
|
|
149
|
+
.object({
|
|
150
|
+
enabled: z.boolean().default(false),
|
|
151
|
+
systemPrompt: z.string().max(8_000).default(""),
|
|
152
|
+
// Audience names the widget is shown to; [] = all audiences.
|
|
153
|
+
audiences: z.array(z.string()).default([]),
|
|
154
|
+
tasks: z.array(ChatbotTaskSchema).max(20).default([]),
|
|
155
|
+
})
|
|
156
|
+
.superRefine((cfg, ctx) => {
|
|
157
|
+
const seen = new Set<string>();
|
|
158
|
+
for (const t of cfg.tasks) {
|
|
159
|
+
if (seen.has(t.id)) {
|
|
160
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `Duplicate task id "${t.id}"` });
|
|
161
|
+
}
|
|
162
|
+
seen.add(t.id);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
export type ChatbotConfig = z.infer<typeof ChatbotConfigSchema>;
|
|
166
|
+
|
|
135
167
|
export const SiteConfigSchema = z.object({
|
|
136
168
|
siteName: z.string().default("Brand Portal"),
|
|
137
169
|
primaryColor: HexColorSchema.default("#009ca6"),
|
|
@@ -153,6 +185,10 @@ export const SiteConfigSchema = z.object({
|
|
|
153
185
|
.nullable()
|
|
154
186
|
.default(null),
|
|
155
187
|
media: MediaConfigSchema.default(MediaConfigSchema.parse({})),
|
|
188
|
+
// Brand chatbot config (editor-owned; the platform kill switch lives in the
|
|
189
|
+
// sites table, not here). Every field defaults so pre-existing
|
|
190
|
+
// site-config.json files keep parsing.
|
|
191
|
+
chatbot: ChatbotConfigSchema.default({ enabled: false, systemPrompt: "", audiences: [], tasks: [] }),
|
|
156
192
|
});
|
|
157
193
|
|
|
158
194
|
export type SiteConfig = z.infer<typeof SiteConfigSchema>;
|
package/src/types/database.ts
CHANGED
|
@@ -150,6 +150,41 @@ export type Database = {
|
|
|
150
150
|
},
|
|
151
151
|
]
|
|
152
152
|
}
|
|
153
|
+
chat_usage: {
|
|
154
|
+
Row: {
|
|
155
|
+
day: string
|
|
156
|
+
input_tokens: number
|
|
157
|
+
output_tokens: number
|
|
158
|
+
requests: number
|
|
159
|
+
site_id: string
|
|
160
|
+
updated_at: string
|
|
161
|
+
}
|
|
162
|
+
Insert: {
|
|
163
|
+
day: string
|
|
164
|
+
input_tokens?: number
|
|
165
|
+
output_tokens?: number
|
|
166
|
+
requests?: number
|
|
167
|
+
site_id: string
|
|
168
|
+
updated_at?: string
|
|
169
|
+
}
|
|
170
|
+
Update: {
|
|
171
|
+
day?: string
|
|
172
|
+
input_tokens?: number
|
|
173
|
+
output_tokens?: number
|
|
174
|
+
requests?: number
|
|
175
|
+
site_id?: string
|
|
176
|
+
updated_at?: string
|
|
177
|
+
}
|
|
178
|
+
Relationships: [
|
|
179
|
+
{
|
|
180
|
+
foreignKeyName: "chat_usage_site_id_fkey"
|
|
181
|
+
columns: ["site_id"]
|
|
182
|
+
isOneToOne: false
|
|
183
|
+
referencedRelation: "sites"
|
|
184
|
+
referencedColumns: ["id"]
|
|
185
|
+
},
|
|
186
|
+
]
|
|
187
|
+
}
|
|
153
188
|
deploy_status: {
|
|
154
189
|
Row: {
|
|
155
190
|
commit_sha: string | null
|
|
@@ -535,6 +570,7 @@ export type Database = {
|
|
|
535
570
|
}
|
|
536
571
|
sites: {
|
|
537
572
|
Row: {
|
|
573
|
+
chatbot_enabled: boolean
|
|
538
574
|
created_at: string | null
|
|
539
575
|
custom_domain: string | null
|
|
540
576
|
failed_step: string | null
|
|
@@ -550,6 +586,7 @@ export type Database = {
|
|
|
550
586
|
subdomain: string | null
|
|
551
587
|
}
|
|
552
588
|
Insert: {
|
|
589
|
+
chatbot_enabled?: boolean
|
|
553
590
|
created_at?: string | null
|
|
554
591
|
custom_domain?: string | null
|
|
555
592
|
failed_step?: string | null
|
|
@@ -565,6 +602,7 @@ export type Database = {
|
|
|
565
602
|
subdomain?: string | null
|
|
566
603
|
}
|
|
567
604
|
Update: {
|
|
605
|
+
chatbot_enabled?: boolean
|
|
568
606
|
created_at?: string | null
|
|
569
607
|
custom_domain?: string | null
|
|
570
608
|
failed_step?: string | null
|
|
@@ -642,6 +680,16 @@ export type Database = {
|
|
|
642
680
|
[_ in never]: never
|
|
643
681
|
}
|
|
644
682
|
Functions: {
|
|
683
|
+
chat_usage_hit: {
|
|
684
|
+
Args: {
|
|
685
|
+
p_day: string
|
|
686
|
+
p_input_tokens: number
|
|
687
|
+
p_output_tokens: number
|
|
688
|
+
p_requests: number
|
|
689
|
+
p_site_id: string
|
|
690
|
+
}
|
|
691
|
+
Returns: undefined
|
|
692
|
+
}
|
|
645
693
|
get_user_id_by_email: { Args: { lookup_email: string }; Returns: string }
|
|
646
694
|
is_site_member: { Args: { p_site_id: string }; Returns: boolean }
|
|
647
695
|
is_site_owner: { Args: { p_site_id: string }; Returns: boolean }
|