@webmaster-droid/web 0.1.0-alpha.1 → 0.1.0
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/index.d.ts +13 -11
- package/dist/index.js +700 -442
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -6,11 +6,36 @@ import {
|
|
|
6
6
|
createElement,
|
|
7
7
|
useContext
|
|
8
8
|
} from "react";
|
|
9
|
+
import sanitizeHtml from "sanitize-html";
|
|
9
10
|
import { jsx } from "react/jsx-runtime";
|
|
10
11
|
var EDITABLE_ROOTS = ["pages.", "layout.", "seo.", "themeTokens."];
|
|
11
12
|
var MAX_PATH_LENGTH = 320;
|
|
12
13
|
var MAX_LABEL_LENGTH = 120;
|
|
13
14
|
var MAX_PREVIEW_LENGTH = 140;
|
|
15
|
+
var RICH_TEXT_ALLOWED_TAGS = [
|
|
16
|
+
"a",
|
|
17
|
+
"b",
|
|
18
|
+
"blockquote",
|
|
19
|
+
"br",
|
|
20
|
+
"code",
|
|
21
|
+
"em",
|
|
22
|
+
"h1",
|
|
23
|
+
"h2",
|
|
24
|
+
"h3",
|
|
25
|
+
"h4",
|
|
26
|
+
"h5",
|
|
27
|
+
"h6",
|
|
28
|
+
"hr",
|
|
29
|
+
"i",
|
|
30
|
+
"li",
|
|
31
|
+
"ol",
|
|
32
|
+
"p",
|
|
33
|
+
"pre",
|
|
34
|
+
"strong",
|
|
35
|
+
"u",
|
|
36
|
+
"ul"
|
|
37
|
+
];
|
|
38
|
+
var RICH_TEXT_ALLOWED_ATTRS = ["href", "title", "aria-label", "rel"];
|
|
14
39
|
var EditableContext = createContext(null);
|
|
15
40
|
function EditableProvider(props) {
|
|
16
41
|
return /* @__PURE__ */ jsx(
|
|
@@ -172,9 +197,28 @@ function parseSelectedEditableFromTarget(target, pagePath) {
|
|
|
172
197
|
}
|
|
173
198
|
return selected;
|
|
174
199
|
}
|
|
175
|
-
function pickStringValue(document2, path, fallback) {
|
|
200
|
+
function pickStringValue(document2, path, fallback, componentName, fallbackPropName) {
|
|
176
201
|
const value = readByPath(document2, path);
|
|
177
|
-
|
|
202
|
+
if (typeof value === "string" && value.trim()) {
|
|
203
|
+
return value;
|
|
204
|
+
}
|
|
205
|
+
if (typeof fallback === "string") {
|
|
206
|
+
return fallback;
|
|
207
|
+
}
|
|
208
|
+
throw new Error(
|
|
209
|
+
`${componentName} missing content for "${path}". Provide a CMS value or set \`${fallbackPropName}\`.`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
function sanitizeRichTextHtml(html) {
|
|
213
|
+
return sanitizeHtml(html, {
|
|
214
|
+
allowedTags: [...RICH_TEXT_ALLOWED_TAGS],
|
|
215
|
+
allowedAttributes: {
|
|
216
|
+
a: [...RICH_TEXT_ALLOWED_ATTRS]
|
|
217
|
+
},
|
|
218
|
+
allowedSchemes: ["http", "https", "mailto", "tel"],
|
|
219
|
+
allowProtocolRelative: false,
|
|
220
|
+
disallowedTagsMode: "discard"
|
|
221
|
+
});
|
|
178
222
|
}
|
|
179
223
|
function EditableText({
|
|
180
224
|
path,
|
|
@@ -185,7 +229,7 @@ function EditableText({
|
|
|
185
229
|
...rest
|
|
186
230
|
}) {
|
|
187
231
|
const { document: document2, enabled } = useEditableDocument();
|
|
188
|
-
const value = pickStringValue(document2, path, fallback);
|
|
232
|
+
const value = pickStringValue(document2, path, fallback, "EditableText", "fallback");
|
|
189
233
|
const attrs = enabled ? editableMeta({
|
|
190
234
|
path,
|
|
191
235
|
label: label ?? path,
|
|
@@ -203,7 +247,8 @@ function EditableRichText({
|
|
|
203
247
|
...rest
|
|
204
248
|
}) {
|
|
205
249
|
const { document: document2, enabled } = useEditableDocument();
|
|
206
|
-
const value = pickStringValue(document2, path, fallback);
|
|
250
|
+
const value = pickStringValue(document2, path, fallback, "EditableRichText", "fallback");
|
|
251
|
+
const sanitizedHtml = sanitizeRichTextHtml(value);
|
|
207
252
|
const attrs = enabled ? editableMeta({
|
|
208
253
|
path,
|
|
209
254
|
label: label ?? path,
|
|
@@ -213,7 +258,7 @@ function EditableRichText({
|
|
|
213
258
|
return createElement(as, {
|
|
214
259
|
...rest,
|
|
215
260
|
...attrs,
|
|
216
|
-
dangerouslySetInnerHTML: { __html:
|
|
261
|
+
dangerouslySetInnerHTML: { __html: sanitizedHtml }
|
|
217
262
|
});
|
|
218
263
|
}
|
|
219
264
|
function EditableImage({
|
|
@@ -225,8 +270,8 @@ function EditableImage({
|
|
|
225
270
|
...rest
|
|
226
271
|
}) {
|
|
227
272
|
const { document: document2, enabled } = useEditableDocument();
|
|
228
|
-
const src = pickStringValue(document2, path, fallbackSrc);
|
|
229
|
-
const alt = altPath ? pickStringValue(document2, altPath, fallbackAlt) : fallbackAlt;
|
|
273
|
+
const src = pickStringValue(document2, path, fallbackSrc, "EditableImage", "fallbackSrc");
|
|
274
|
+
const alt = altPath ? pickStringValue(document2, altPath, fallbackAlt, "EditableImage", "fallbackAlt") : fallbackAlt ?? "";
|
|
230
275
|
const attrs = enabled ? editableMeta({
|
|
231
276
|
path,
|
|
232
277
|
label: label ?? path,
|
|
@@ -245,8 +290,14 @@ function EditableLink({
|
|
|
245
290
|
...rest
|
|
246
291
|
}) {
|
|
247
292
|
const { document: document2, enabled } = useEditableDocument();
|
|
248
|
-
const href = pickStringValue(document2, hrefPath, fallbackHref);
|
|
249
|
-
const text = pickStringValue(
|
|
293
|
+
const href = pickStringValue(document2, hrefPath, fallbackHref, "EditableLink", "fallbackHref");
|
|
294
|
+
const text = pickStringValue(
|
|
295
|
+
document2,
|
|
296
|
+
labelPath,
|
|
297
|
+
fallbackLabel,
|
|
298
|
+
"EditableLink",
|
|
299
|
+
"fallbackLabel"
|
|
300
|
+
);
|
|
250
301
|
const attrs = enabled ? editableMeta({
|
|
251
302
|
path: labelPath,
|
|
252
303
|
label: label ?? labelPath,
|
|
@@ -686,18 +737,7 @@ function useWebmasterDroid() {
|
|
|
686
737
|
return context;
|
|
687
738
|
}
|
|
688
739
|
|
|
689
|
-
// src/overlay.
|
|
690
|
-
import {
|
|
691
|
-
useCallback,
|
|
692
|
-
useEffect as useEffect2,
|
|
693
|
-
useMemo as useMemo2,
|
|
694
|
-
useRef,
|
|
695
|
-
useState as useState2
|
|
696
|
-
} from "react";
|
|
697
|
-
import ReactMarkdown from "react-markdown";
|
|
698
|
-
import remarkGfm from "remark-gfm";
|
|
699
|
-
import { REQUIRED_PUBLISH_CONFIRMATION } from "@webmaster-droid/contracts";
|
|
700
|
-
import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
740
|
+
// src/overlay/utils.ts
|
|
701
741
|
function createMessage(role, text, status) {
|
|
702
742
|
return {
|
|
703
743
|
id: `${role}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -786,7 +826,394 @@ function kindIcon(kind) {
|
|
|
786
826
|
}
|
|
787
827
|
return "TXT";
|
|
788
828
|
}
|
|
789
|
-
|
|
829
|
+
var OVERLAY_FONT_FAMILY = "var(--font-ibm-plex-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
|
|
830
|
+
|
|
831
|
+
// src/overlay/components.tsx
|
|
832
|
+
import ReactMarkdown from "react-markdown";
|
|
833
|
+
import remarkGfm from "remark-gfm";
|
|
834
|
+
import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
835
|
+
function OverlayHeader({
|
|
836
|
+
isAuthenticated,
|
|
837
|
+
publishState,
|
|
838
|
+
activeTab,
|
|
839
|
+
historyCount,
|
|
840
|
+
clearChatDisabled,
|
|
841
|
+
onPublish,
|
|
842
|
+
onTabChange,
|
|
843
|
+
onClearChat,
|
|
844
|
+
onClose
|
|
845
|
+
}) {
|
|
846
|
+
return /* @__PURE__ */ jsx3("header", { className: "border-b border-stone-300 bg-[#f3eee5] p-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
847
|
+
isAuthenticated ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
848
|
+
/* @__PURE__ */ jsx3(
|
|
849
|
+
"span",
|
|
850
|
+
{
|
|
851
|
+
className: `rounded border px-1.5 py-0.5 text-[10px] font-medium leading-4 ${publishState === "Published" ? "border-stone-300 bg-[#ece5d9] text-stone-600" : "border-stone-500 bg-[#ded4c3] text-stone-800"}`,
|
|
852
|
+
children: publishState
|
|
853
|
+
}
|
|
854
|
+
),
|
|
855
|
+
/* @__PURE__ */ jsx3(
|
|
856
|
+
"button",
|
|
857
|
+
{
|
|
858
|
+
type: "button",
|
|
859
|
+
className: "rounded border border-stone-700 bg-stone-800 px-2 py-1 text-[11px] font-semibold leading-4 text-stone-100 hover:bg-stone-700 disabled:cursor-not-allowed disabled:opacity-50",
|
|
860
|
+
onClick: onPublish,
|
|
861
|
+
disabled: !isAuthenticated,
|
|
862
|
+
children: "Publish"
|
|
863
|
+
}
|
|
864
|
+
),
|
|
865
|
+
/* @__PURE__ */ jsxs("div", { className: "inline-flex rounded-md border border-stone-300 bg-[#e8dfd1] p-0.5", children: [
|
|
866
|
+
/* @__PURE__ */ jsx3(
|
|
867
|
+
"button",
|
|
868
|
+
{
|
|
869
|
+
type: "button",
|
|
870
|
+
className: `rounded px-2 py-1 text-[11px] font-medium leading-4 ${activeTab === "chat" ? "bg-[#f7f2e8] text-stone-900 shadow-sm" : "text-stone-600 hover:text-stone-900"}`,
|
|
871
|
+
onClick: () => onTabChange("chat"),
|
|
872
|
+
children: "Chat"
|
|
873
|
+
}
|
|
874
|
+
),
|
|
875
|
+
/* @__PURE__ */ jsxs(
|
|
876
|
+
"button",
|
|
877
|
+
{
|
|
878
|
+
type: "button",
|
|
879
|
+
className: `rounded px-2 py-1 text-[11px] font-medium leading-4 ${activeTab === "history" ? "bg-[#f7f2e8] text-stone-900 shadow-sm" : "text-stone-600 hover:text-stone-900"}`,
|
|
880
|
+
onClick: () => onTabChange("history"),
|
|
881
|
+
children: [
|
|
882
|
+
"History (",
|
|
883
|
+
historyCount,
|
|
884
|
+
")"
|
|
885
|
+
]
|
|
886
|
+
}
|
|
887
|
+
)
|
|
888
|
+
] })
|
|
889
|
+
] }) : /* @__PURE__ */ jsx3("h2", { className: "text-[12px] font-semibold text-stone-700", children: "Login" }),
|
|
890
|
+
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1", children: [
|
|
891
|
+
isAuthenticated ? /* @__PURE__ */ jsx3(
|
|
892
|
+
"button",
|
|
893
|
+
{
|
|
894
|
+
type: "button",
|
|
895
|
+
"aria-label": "Clear chat",
|
|
896
|
+
title: "Clear chat",
|
|
897
|
+
disabled: clearChatDisabled,
|
|
898
|
+
className: "inline-flex h-6 w-6 items-center justify-center rounded border border-stone-300 text-stone-600 hover:bg-[#efe8dc] hover:text-stone-800 disabled:cursor-not-allowed disabled:opacity-50",
|
|
899
|
+
onClick: onClearChat,
|
|
900
|
+
children: /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 20 20", fill: "none", className: "h-3.5 w-3.5", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(
|
|
901
|
+
"path",
|
|
902
|
+
{
|
|
903
|
+
d: "M4.5 5.5H15.5M8 3.75H12M7 7.5V13.5M10 7.5V13.5M13 7.5V13.5M6.5 5.5L7 15C7.03 15.6 7.53 16.08 8.13 16.08H11.87C12.47 16.08 12.97 15.6 13 15L13.5 5.5",
|
|
904
|
+
stroke: "currentColor",
|
|
905
|
+
strokeWidth: "1.4",
|
|
906
|
+
strokeLinecap: "round",
|
|
907
|
+
strokeLinejoin: "round"
|
|
908
|
+
}
|
|
909
|
+
) })
|
|
910
|
+
}
|
|
911
|
+
) : null,
|
|
912
|
+
/* @__PURE__ */ jsx3(
|
|
913
|
+
"button",
|
|
914
|
+
{
|
|
915
|
+
type: "button",
|
|
916
|
+
className: "rounded border border-stone-300 px-2 py-1 text-[11px] leading-4 text-stone-700 hover:bg-[#efe8dc]",
|
|
917
|
+
onClick: onClose,
|
|
918
|
+
children: "Close"
|
|
919
|
+
}
|
|
920
|
+
)
|
|
921
|
+
] })
|
|
922
|
+
] }) });
|
|
923
|
+
}
|
|
924
|
+
function OverlayLoginPanel({
|
|
925
|
+
authConfigured,
|
|
926
|
+
email,
|
|
927
|
+
password,
|
|
928
|
+
signingIn,
|
|
929
|
+
onEmailChange,
|
|
930
|
+
onPasswordChange,
|
|
931
|
+
onSignIn
|
|
932
|
+
}) {
|
|
933
|
+
return /* @__PURE__ */ jsx3("section", { className: "flex min-h-0 flex-1 items-center justify-center bg-[#ece7dd] p-3", children: !authConfigured ? /* @__PURE__ */ jsx3("div", { className: "w-full max-w-sm rounded border border-red-300 bg-[#f8f3e9] p-3 text-[11px] leading-4 text-red-700", children: "Missing Supabase config (`supabaseUrl` / `supabaseAnonKey`)." }) : /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded border border-stone-300 bg-[#f8f3e9] p-3", children: [
|
|
934
|
+
/* @__PURE__ */ jsx3("h3", { className: "mb-2 text-[12px] font-semibold text-stone-700", children: "Sign in" }),
|
|
935
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
936
|
+
/* @__PURE__ */ jsx3(
|
|
937
|
+
"input",
|
|
938
|
+
{
|
|
939
|
+
type: "text",
|
|
940
|
+
value: email,
|
|
941
|
+
onChange: (event) => onEmailChange(event.target.value),
|
|
942
|
+
placeholder: "login",
|
|
943
|
+
className: "w-full rounded border border-stone-300 bg-[#f4efe6] px-2 py-1.5 text-[12px] text-stone-900 outline-none focus:border-stone-500"
|
|
944
|
+
}
|
|
945
|
+
),
|
|
946
|
+
/* @__PURE__ */ jsx3(
|
|
947
|
+
"input",
|
|
948
|
+
{
|
|
949
|
+
type: "password",
|
|
950
|
+
value: password,
|
|
951
|
+
onChange: (event) => onPasswordChange(event.target.value),
|
|
952
|
+
placeholder: "Password",
|
|
953
|
+
className: "w-full rounded border border-stone-300 bg-[#f4efe6] px-2 py-1.5 text-[12px] text-stone-900 outline-none focus:border-stone-500"
|
|
954
|
+
}
|
|
955
|
+
),
|
|
956
|
+
/* @__PURE__ */ jsx3(
|
|
957
|
+
"button",
|
|
958
|
+
{
|
|
959
|
+
type: "button",
|
|
960
|
+
onClick: onSignIn,
|
|
961
|
+
disabled: signingIn || !email.trim() || !password,
|
|
962
|
+
className: "w-full rounded border border-stone-700 bg-stone-800 px-2 py-1.5 text-[12px] font-medium text-stone-100 hover:bg-stone-700 disabled:cursor-not-allowed disabled:opacity-50",
|
|
963
|
+
children: signingIn ? "Signing in" : "Sign in"
|
|
964
|
+
}
|
|
965
|
+
)
|
|
966
|
+
] })
|
|
967
|
+
] }) });
|
|
968
|
+
}
|
|
969
|
+
function OverlayChatPanel({
|
|
970
|
+
messages,
|
|
971
|
+
chatEndRef,
|
|
972
|
+
showAssistantAvatarImage,
|
|
973
|
+
assistantAvatarUrl,
|
|
974
|
+
assistantAvatarFallbackLabel,
|
|
975
|
+
onAssistantAvatarError,
|
|
976
|
+
showModelPicker,
|
|
977
|
+
selectableModels,
|
|
978
|
+
modelId,
|
|
979
|
+
sending,
|
|
980
|
+
onModelChange,
|
|
981
|
+
selectedElement,
|
|
982
|
+
onClearSelectedElement,
|
|
983
|
+
message,
|
|
984
|
+
onMessageChange,
|
|
985
|
+
onMessageKeyDown,
|
|
986
|
+
onSend,
|
|
987
|
+
isAuthenticated
|
|
988
|
+
}) {
|
|
989
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
990
|
+
/* @__PURE__ */ jsxs("section", { className: "flex-1 space-y-1 overflow-auto bg-[#ece7dd] p-2", children: [
|
|
991
|
+
messages.map((entry) => {
|
|
992
|
+
const isAssistant = entry.role === "assistant";
|
|
993
|
+
const isPendingAssistant = isAssistant && entry.status === "pending";
|
|
994
|
+
return /* @__PURE__ */ jsx3(
|
|
995
|
+
"div",
|
|
996
|
+
{
|
|
997
|
+
className: entry.role === "tool" ? "max-w-[96%] px-0.5 py-0 text-[10px] leading-tight text-stone-500" : `max-w-[92%] rounded-md py-1.5 text-[12px] leading-4 ${entry.role === "user" ? "ml-auto bg-[#2e2b27] px-2 text-stone-50" : entry.role === "thinking" ? "bg-[#e3dbce] px-2 text-stone-700" : isAssistant ? "relative border border-[#d6ccbb] bg-[#f8f3e9] pl-8 pr-2 text-stone-800" : "bg-[#ddd2bf] px-2 text-stone-800"}`,
|
|
998
|
+
children: entry.role === "tool" ? /* @__PURE__ */ jsx3("span", { children: entry.text }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
999
|
+
isAssistant ? showAssistantAvatarImage ? /* @__PURE__ */ jsx3(
|
|
1000
|
+
"img",
|
|
1001
|
+
{
|
|
1002
|
+
src: assistantAvatarUrl,
|
|
1003
|
+
alt: "",
|
|
1004
|
+
"aria-hidden": "true",
|
|
1005
|
+
className: `pointer-events-none absolute left-2 top-1.5 h-[18px] w-[18px] select-none rounded-full border border-[#d6ccbb] bg-[#efe8dc] object-cover ${isPendingAssistant ? "animate-pulse" : ""}`,
|
|
1006
|
+
onError: onAssistantAvatarError
|
|
1007
|
+
}
|
|
1008
|
+
) : /* @__PURE__ */ jsx3(
|
|
1009
|
+
"span",
|
|
1010
|
+
{
|
|
1011
|
+
"aria-hidden": "true",
|
|
1012
|
+
className: `pointer-events-none absolute left-2 top-1.5 inline-flex h-[18px] w-[18px] select-none items-center justify-center rounded-full border border-[#d6ccbb] bg-[#efe8dc] text-[9px] font-semibold text-stone-700 ${isPendingAssistant ? "animate-pulse" : ""}`,
|
|
1013
|
+
children: assistantAvatarFallbackLabel
|
|
1014
|
+
}
|
|
1015
|
+
) : null,
|
|
1016
|
+
/* @__PURE__ */ jsx3("div", { className: "max-w-none text-inherit [&_code]:rounded [&_code]:bg-stone-900/10 [&_code]:px-1 [&_ol]:list-decimal [&_ol]:pl-4 [&_p]:mb-1 [&_p:last-child]:mb-0 [&_ul]:list-disc [&_ul]:pl-4", children: isPendingAssistant && !entry.text.trim() ? /* @__PURE__ */ jsx3("span", { className: "block h-4", "aria-hidden": "true" }) : /* @__PURE__ */ jsx3(ReactMarkdown, { remarkPlugins: [remarkGfm], children: entry.text }) })
|
|
1017
|
+
] })
|
|
1018
|
+
},
|
|
1019
|
+
entry.id
|
|
1020
|
+
);
|
|
1021
|
+
}),
|
|
1022
|
+
/* @__PURE__ */ jsx3("div", { ref: chatEndRef })
|
|
1023
|
+
] }),
|
|
1024
|
+
/* @__PURE__ */ jsxs("footer", { className: "border-t border-stone-300 bg-[#f3eee5] p-2", children: [
|
|
1025
|
+
showModelPicker && selectableModels.length > 1 ? /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center gap-1.5", children: [
|
|
1026
|
+
/* @__PURE__ */ jsx3(
|
|
1027
|
+
"label",
|
|
1028
|
+
{
|
|
1029
|
+
htmlFor: "admin-model-picker",
|
|
1030
|
+
className: "text-[10px] font-semibold uppercase tracking-wide text-stone-600",
|
|
1031
|
+
children: "Model"
|
|
1032
|
+
}
|
|
1033
|
+
),
|
|
1034
|
+
/* @__PURE__ */ jsx3(
|
|
1035
|
+
"select",
|
|
1036
|
+
{
|
|
1037
|
+
id: "admin-model-picker",
|
|
1038
|
+
value: modelId ?? selectableModels[0]?.id,
|
|
1039
|
+
onChange: (event) => onModelChange(event.target.value),
|
|
1040
|
+
disabled: sending,
|
|
1041
|
+
className: "h-7 min-w-0 flex-1 rounded border border-stone-300 bg-[#f7f2e8] px-2 text-[11px] text-stone-800 outline-none focus:border-stone-500 disabled:cursor-not-allowed disabled:opacity-60",
|
|
1042
|
+
children: selectableModels.map((option) => /* @__PURE__ */ jsx3("option", { value: option.id, children: option.label }, option.id))
|
|
1043
|
+
}
|
|
1044
|
+
)
|
|
1045
|
+
] }) : null,
|
|
1046
|
+
selectedElement ? /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center gap-1 rounded border border-stone-300 bg-[#e8dfd1] px-1.5 py-1", children: [
|
|
1047
|
+
/* @__PURE__ */ jsx3("span", { className: "inline-flex shrink-0 items-center justify-center rounded border border-stone-300 bg-[#f7f2e8] px-1 py-0.5 text-[9px] font-semibold text-stone-700", children: kindIcon(selectedElement.kind) }),
|
|
1048
|
+
/* @__PURE__ */ jsxs("p", { className: "min-w-0 flex-1 truncate text-[10px] leading-3.5 text-stone-600", children: [
|
|
1049
|
+
/* @__PURE__ */ jsx3("span", { className: "font-semibold text-stone-800", children: selectedElement.label }),
|
|
1050
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
1051
|
+
" \xB7 ",
|
|
1052
|
+
selectedElement.path
|
|
1053
|
+
] }),
|
|
1054
|
+
selectedElement.preview ? /* @__PURE__ */ jsxs("span", { children: [
|
|
1055
|
+
" \xB7 ",
|
|
1056
|
+
selectedElement.preview
|
|
1057
|
+
] }) : null
|
|
1058
|
+
] }),
|
|
1059
|
+
/* @__PURE__ */ jsx3(
|
|
1060
|
+
"button",
|
|
1061
|
+
{
|
|
1062
|
+
type: "button",
|
|
1063
|
+
"aria-label": "Clear selected element",
|
|
1064
|
+
title: "Clear selected element",
|
|
1065
|
+
className: "inline-flex h-5 w-5 shrink-0 items-center justify-center rounded border border-stone-300 bg-[#f7f2e8] text-stone-700 hover:bg-[#efe8dc]",
|
|
1066
|
+
onClick: onClearSelectedElement,
|
|
1067
|
+
children: /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 20 20", fill: "none", className: "h-3 w-3", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(
|
|
1068
|
+
"path",
|
|
1069
|
+
{
|
|
1070
|
+
d: "M5 5L15 15M15 5L5 15",
|
|
1071
|
+
stroke: "currentColor",
|
|
1072
|
+
strokeWidth: "1.5",
|
|
1073
|
+
strokeLinecap: "round"
|
|
1074
|
+
}
|
|
1075
|
+
) })
|
|
1076
|
+
}
|
|
1077
|
+
)
|
|
1078
|
+
] }) : null,
|
|
1079
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
|
|
1080
|
+
/* @__PURE__ */ jsx3(
|
|
1081
|
+
"textarea",
|
|
1082
|
+
{
|
|
1083
|
+
value: message,
|
|
1084
|
+
onChange: (event) => onMessageChange(event.target.value),
|
|
1085
|
+
onKeyDown: onMessageKeyDown,
|
|
1086
|
+
rows: 2,
|
|
1087
|
+
placeholder: "Ask the agent to edit text, image URLs, or theme tokens",
|
|
1088
|
+
className: "flex-1 resize-none rounded border border-stone-300 bg-[#f4efe6] px-2 py-1.5 text-[12px] leading-4 text-stone-900 outline-none placeholder:text-stone-500 focus:border-stone-500"
|
|
1089
|
+
}
|
|
1090
|
+
),
|
|
1091
|
+
/* @__PURE__ */ jsx3(
|
|
1092
|
+
"button",
|
|
1093
|
+
{
|
|
1094
|
+
type: "button",
|
|
1095
|
+
onClick: onSend,
|
|
1096
|
+
disabled: !isAuthenticated || sending || !message.trim(),
|
|
1097
|
+
className: "rounded border border-stone-500 bg-stone-600 px-3 py-1.5 text-[12px] font-semibold text-stone-100 hover:bg-stone-700 disabled:cursor-not-allowed disabled:opacity-50",
|
|
1098
|
+
children: sending ? "Sending" : "Send"
|
|
1099
|
+
}
|
|
1100
|
+
)
|
|
1101
|
+
] })
|
|
1102
|
+
] })
|
|
1103
|
+
] });
|
|
1104
|
+
}
|
|
1105
|
+
function OverlayHistoryPanel({
|
|
1106
|
+
history,
|
|
1107
|
+
deletingCheckpointId,
|
|
1108
|
+
onRestorePublished,
|
|
1109
|
+
onRestoreCheckpoint,
|
|
1110
|
+
onDeleteCheckpoint
|
|
1111
|
+
}) {
|
|
1112
|
+
return /* @__PURE__ */ jsx3("section", { className: "flex min-h-0 flex-1 flex-col p-2 text-[11px] leading-4", children: /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-2 overflow-hidden", children: [
|
|
1113
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded border border-stone-300 bg-[#f8f3e9]", children: [
|
|
1114
|
+
/* @__PURE__ */ jsxs("div", { className: "border-b border-stone-200 px-2 py-1 font-semibold text-stone-700", children: [
|
|
1115
|
+
"Published (",
|
|
1116
|
+
history.published.length,
|
|
1117
|
+
")"
|
|
1118
|
+
] }),
|
|
1119
|
+
/* @__PURE__ */ jsx3("div", { className: "max-h-40 overflow-auto px-2 py-1.5", children: history.published.length > 0 ? /* @__PURE__ */ jsx3("div", { className: "space-y-1", children: history.published.map((item) => /* @__PURE__ */ jsxs(
|
|
1120
|
+
"div",
|
|
1121
|
+
{
|
|
1122
|
+
className: "flex items-center justify-between gap-2 rounded border border-stone-200 bg-[#f2ecdf] px-2 py-1",
|
|
1123
|
+
children: [
|
|
1124
|
+
/* @__PURE__ */ jsx3("span", { className: "truncate text-[10px] text-stone-700", children: formatHistoryTime(item.createdAt) }),
|
|
1125
|
+
/* @__PURE__ */ jsx3(
|
|
1126
|
+
"button",
|
|
1127
|
+
{
|
|
1128
|
+
type: "button",
|
|
1129
|
+
className: "rounded border border-stone-300 bg-[#f7f2e8] px-1.5 py-0.5 text-[10px] text-stone-700 hover:bg-[#efe8dc]",
|
|
1130
|
+
onClick: () => onRestorePublished(item),
|
|
1131
|
+
children: "Restore"
|
|
1132
|
+
}
|
|
1133
|
+
)
|
|
1134
|
+
]
|
|
1135
|
+
},
|
|
1136
|
+
`pub-${item.id}`
|
|
1137
|
+
)) }) : /* @__PURE__ */ jsx3("p", { className: "text-[10px] text-stone-500", children: "No published snapshots." }) })
|
|
1138
|
+
] }),
|
|
1139
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col rounded border border-stone-300 bg-[#f8f3e9]", children: [
|
|
1140
|
+
/* @__PURE__ */ jsxs("div", { className: "border-b border-stone-200 px-2 py-1 font-semibold text-stone-700", children: [
|
|
1141
|
+
"Checkpoints (",
|
|
1142
|
+
history.checkpoints.length,
|
|
1143
|
+
")"
|
|
1144
|
+
] }),
|
|
1145
|
+
/* @__PURE__ */ jsx3("div", { className: "min-h-0 flex-1 overflow-auto px-2 py-1.5", children: history.checkpoints.length > 0 ? /* @__PURE__ */ jsx3("div", { className: "space-y-1", children: history.checkpoints.map((item) => /* @__PURE__ */ jsxs(
|
|
1146
|
+
"div",
|
|
1147
|
+
{
|
|
1148
|
+
className: "flex items-start justify-between gap-2 rounded border border-stone-200 bg-[#f2ecdf] px-2 py-1",
|
|
1149
|
+
children: [
|
|
1150
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1151
|
+
/* @__PURE__ */ jsx3("p", { className: "truncate text-[10px] text-stone-700", children: formatHistoryTime(item.createdAt) }),
|
|
1152
|
+
item.reason ? /* @__PURE__ */ jsx3("p", { className: "truncate text-[10px] text-stone-500", children: item.reason }) : null
|
|
1153
|
+
] }),
|
|
1154
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
1155
|
+
/* @__PURE__ */ jsx3(
|
|
1156
|
+
"button",
|
|
1157
|
+
{
|
|
1158
|
+
type: "button",
|
|
1159
|
+
disabled: deletingCheckpointId === item.id,
|
|
1160
|
+
className: "rounded border border-stone-300 bg-[#f7f2e8] px-1.5 py-0.5 text-[10px] text-stone-700 hover:bg-[#efe8dc] disabled:cursor-not-allowed disabled:opacity-50",
|
|
1161
|
+
onClick: () => onRestoreCheckpoint(item),
|
|
1162
|
+
children: "Restore"
|
|
1163
|
+
}
|
|
1164
|
+
),
|
|
1165
|
+
/* @__PURE__ */ jsx3(
|
|
1166
|
+
"button",
|
|
1167
|
+
{
|
|
1168
|
+
type: "button",
|
|
1169
|
+
"aria-label": "Delete checkpoint",
|
|
1170
|
+
title: "Delete checkpoint",
|
|
1171
|
+
disabled: deletingCheckpointId === item.id,
|
|
1172
|
+
className: "inline-flex h-6 w-6 items-center justify-center rounded border border-stone-300 bg-[#f7f2e8] text-stone-700 hover:bg-[#efe8dc] disabled:cursor-not-allowed disabled:opacity-50",
|
|
1173
|
+
onClick: () => onDeleteCheckpoint(item),
|
|
1174
|
+
children: /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 20 20", fill: "none", className: "h-3.5 w-3.5", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(
|
|
1175
|
+
"path",
|
|
1176
|
+
{
|
|
1177
|
+
d: "M4.5 5.5H15.5M8 3.75H12M7 7.5V13.5M10 7.5V13.5M13 7.5V13.5M6.5 5.5L7 15C7.03 15.6 7.53 16.08 8.13 16.08H11.87C12.47 16.08 12.97 15.6 13 15L13.5 5.5",
|
|
1178
|
+
stroke: "currentColor",
|
|
1179
|
+
strokeWidth: "1.4",
|
|
1180
|
+
strokeLinecap: "round",
|
|
1181
|
+
strokeLinejoin: "round"
|
|
1182
|
+
}
|
|
1183
|
+
) })
|
|
1184
|
+
}
|
|
1185
|
+
)
|
|
1186
|
+
] })
|
|
1187
|
+
]
|
|
1188
|
+
},
|
|
1189
|
+
`cp-${item.id}`
|
|
1190
|
+
)) }) : /* @__PURE__ */ jsx3("p", { className: "text-[10px] text-stone-500", children: "No checkpoints yet." }) })
|
|
1191
|
+
] })
|
|
1192
|
+
] }) });
|
|
1193
|
+
}
|
|
1194
|
+
function OverlayLauncherButton({ onOpen }) {
|
|
1195
|
+
return /* @__PURE__ */ jsx3(
|
|
1196
|
+
"button",
|
|
1197
|
+
{
|
|
1198
|
+
type: "button",
|
|
1199
|
+
onClick: onOpen,
|
|
1200
|
+
className: "fixed bottom-4 right-4 z-[100] rounded-full border border-stone-600 bg-stone-700 px-4 py-2 text-[12px] font-semibold text-stone-100 shadow-xl hover:bg-stone-800",
|
|
1201
|
+
style: { fontFamily: OVERLAY_FONT_FAMILY },
|
|
1202
|
+
children: "Chat to Webmaster"
|
|
1203
|
+
}
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// src/overlay/controller.ts
|
|
1208
|
+
import {
|
|
1209
|
+
useCallback,
|
|
1210
|
+
useEffect as useEffect2,
|
|
1211
|
+
useMemo as useMemo2,
|
|
1212
|
+
useRef,
|
|
1213
|
+
useState as useState2
|
|
1214
|
+
} from "react";
|
|
1215
|
+
import { REQUIRED_PUBLISH_CONFIRMATION } from "@webmaster-droid/contracts";
|
|
1216
|
+
function useOverlayController() {
|
|
790
1217
|
const {
|
|
791
1218
|
config,
|
|
792
1219
|
isAdminMode,
|
|
@@ -878,11 +1305,7 @@ function WebmasterDroidOverlay() {
|
|
|
878
1305
|
document.removeEventListener("click", onDocumentClickCapture, true);
|
|
879
1306
|
};
|
|
880
1307
|
}, [isAdminMode, isOpen, setSelectedElement]);
|
|
881
|
-
const
|
|
882
|
-
if (!isAdminMode) {
|
|
883
|
-
return null;
|
|
884
|
-
}
|
|
885
|
-
const signInWithPassword = async () => {
|
|
1308
|
+
const signInWithPassword = useCallback(async () => {
|
|
886
1309
|
if (!supabase) {
|
|
887
1310
|
setMessages((prev) => [
|
|
888
1311
|
...prev,
|
|
@@ -915,8 +1338,8 @@ function WebmasterDroidOverlay() {
|
|
|
915
1338
|
} finally {
|
|
916
1339
|
setSigningIn(false);
|
|
917
1340
|
}
|
|
918
|
-
};
|
|
919
|
-
const onSend = async () => {
|
|
1341
|
+
}, [email, password, supabase]);
|
|
1342
|
+
const onSend = useCallback(async () => {
|
|
920
1343
|
if (!token || !message.trim() || sending) {
|
|
921
1344
|
return;
|
|
922
1345
|
}
|
|
@@ -1020,14 +1443,28 @@ function WebmasterDroidOverlay() {
|
|
|
1020
1443
|
} catch (error) {
|
|
1021
1444
|
const detail = error instanceof Error ? error.message : "Chat failed.";
|
|
1022
1445
|
const pendingAssistantId = pendingAssistantIdRef.current;
|
|
1023
|
-
setMessages((prev) => [
|
|
1446
|
+
setMessages((prev) => [
|
|
1447
|
+
...removeMessageById(prev, pendingAssistantId),
|
|
1448
|
+
createMessage("system", detail)
|
|
1449
|
+
]);
|
|
1024
1450
|
pendingAssistantIdRef.current = null;
|
|
1025
1451
|
} finally {
|
|
1026
1452
|
pendingAssistantIdRef.current = null;
|
|
1027
1453
|
setSending(false);
|
|
1028
1454
|
}
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1455
|
+
}, [
|
|
1456
|
+
config.apiBaseUrl,
|
|
1457
|
+
includeThinking,
|
|
1458
|
+
message,
|
|
1459
|
+
messages,
|
|
1460
|
+
modelId,
|
|
1461
|
+
requestRefresh,
|
|
1462
|
+
refreshHistory,
|
|
1463
|
+
selectedElement,
|
|
1464
|
+
sending,
|
|
1465
|
+
token
|
|
1466
|
+
]);
|
|
1467
|
+
const onPublish = useCallback(async () => {
|
|
1031
1468
|
if (!token) {
|
|
1032
1469
|
return;
|
|
1033
1470
|
}
|
|
@@ -1051,53 +1488,59 @@ function WebmasterDroidOverlay() {
|
|
|
1051
1488
|
const detail = error instanceof Error ? error.message : "Publish failed.";
|
|
1052
1489
|
setMessages((prev) => [...prev, createMessage("system", detail)]);
|
|
1053
1490
|
}
|
|
1054
|
-
};
|
|
1055
|
-
const onRollback =
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1491
|
+
}, [config.apiBaseUrl, requestRefresh, refreshHistory, token]);
|
|
1492
|
+
const onRollback = useCallback(
|
|
1493
|
+
async (request, label) => {
|
|
1494
|
+
if (!token) {
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
try {
|
|
1498
|
+
await rollbackDraft(config.apiBaseUrl, token, request);
|
|
1499
|
+
requestRefresh();
|
|
1500
|
+
await refreshHistory(false);
|
|
1501
|
+
setMessages((prev) => [
|
|
1502
|
+
...prev,
|
|
1503
|
+
createMessage("system", `Draft restored from ${label}.`)
|
|
1504
|
+
]);
|
|
1505
|
+
} catch (error) {
|
|
1506
|
+
const detail = error instanceof Error ? error.message : "Rollback failed.";
|
|
1507
|
+
setMessages((prev) => [...prev, createMessage("system", detail)]);
|
|
1508
|
+
}
|
|
1509
|
+
},
|
|
1510
|
+
[config.apiBaseUrl, requestRefresh, refreshHistory, token]
|
|
1511
|
+
);
|
|
1512
|
+
const onDeleteCheckpoint = useCallback(
|
|
1513
|
+
async (checkpoint) => {
|
|
1514
|
+
if (!token || deletingCheckpointId) {
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
const timestampLabel = formatHistoryTime(checkpoint.createdAt);
|
|
1518
|
+
const reasonLine = checkpoint.reason ? `
|
|
1078
1519
|
Reason: ${checkpoint.reason}` : "";
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1520
|
+
const approved = window.confirm(
|
|
1521
|
+
`Delete checkpoint from ${timestampLabel}? This cannot be undone.${reasonLine}`
|
|
1522
|
+
);
|
|
1523
|
+
if (!approved) {
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
setDeletingCheckpointId(checkpoint.id);
|
|
1527
|
+
try {
|
|
1528
|
+
await deleteCheckpoint(config.apiBaseUrl, token, { checkpointId: checkpoint.id });
|
|
1529
|
+
await refreshHistory(false);
|
|
1530
|
+
setMessages((prev) => [
|
|
1531
|
+
...prev,
|
|
1532
|
+
createMessage("system", `Deleted checkpoint from ${timestampLabel}.`)
|
|
1533
|
+
]);
|
|
1534
|
+
} catch (error) {
|
|
1535
|
+
const detail = error instanceof Error ? error.message : "Delete checkpoint failed.";
|
|
1536
|
+
setMessages((prev) => [...prev, createMessage("system", detail)]);
|
|
1537
|
+
} finally {
|
|
1538
|
+
setDeletingCheckpointId((current) => current === checkpoint.id ? null : current);
|
|
1539
|
+
}
|
|
1540
|
+
},
|
|
1541
|
+
[config.apiBaseUrl, deletingCheckpointId, refreshHistory, token]
|
|
1542
|
+
);
|
|
1543
|
+
const onClearChat = useCallback(() => {
|
|
1101
1544
|
if (sending) {
|
|
1102
1545
|
return;
|
|
1103
1546
|
}
|
|
@@ -1108,372 +1551,180 @@ Reason: ${checkpoint.reason}` : "";
|
|
|
1108
1551
|
setMessages([]);
|
|
1109
1552
|
setMessage("");
|
|
1110
1553
|
clearSelectedElement();
|
|
1111
|
-
};
|
|
1112
|
-
const onMessageKeyDown = (
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1554
|
+
}, [clearSelectedElement, message, messages.length, selectedElement, sending]);
|
|
1555
|
+
const onMessageKeyDown = useCallback(
|
|
1556
|
+
(event) => {
|
|
1557
|
+
if (event.key !== "Enter" || event.shiftKey || event.nativeEvent.isComposing) {
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
event.preventDefault();
|
|
1561
|
+
if (!isAuthenticated || sending || !message.trim()) {
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
void onSend();
|
|
1565
|
+
},
|
|
1566
|
+
[isAuthenticated, message, onSend, sending]
|
|
1567
|
+
);
|
|
1568
|
+
const latestPublished = useMemo2(
|
|
1569
|
+
() => history.published.reduce((max, item) => {
|
|
1570
|
+
const value = historyTimestamp(item.createdAt);
|
|
1571
|
+
if (value === null) {
|
|
1572
|
+
return max;
|
|
1573
|
+
}
|
|
1574
|
+
return max === null ? value : Math.max(max, value);
|
|
1575
|
+
}, null),
|
|
1576
|
+
[history.published]
|
|
1577
|
+
);
|
|
1578
|
+
const latestCheckpoint = useMemo2(
|
|
1579
|
+
() => history.checkpoints.reduce((max, item) => {
|
|
1580
|
+
const value = historyTimestamp(item.createdAt);
|
|
1581
|
+
if (value === null) {
|
|
1582
|
+
return max;
|
|
1583
|
+
}
|
|
1584
|
+
return max === null ? value : Math.max(max, value);
|
|
1585
|
+
}, null),
|
|
1586
|
+
[history.checkpoints]
|
|
1587
|
+
);
|
|
1136
1588
|
const publishState = latestCheckpoint !== null && (latestPublished === null || latestCheckpoint > latestPublished) ? "Unpublished" : "Published";
|
|
1137
1589
|
const assistantAvatarFallbackLabel = (config.assistantAvatarFallback || "W").trim().charAt(0).toUpperCase() || "W";
|
|
1138
1590
|
const showAssistantAvatarImage = Boolean(config.assistantAvatarUrl) && !assistantAvatarFailed;
|
|
1139
|
-
return
|
|
1591
|
+
return {
|
|
1592
|
+
isAdminMode,
|
|
1593
|
+
isAuthenticated,
|
|
1594
|
+
authConfigured,
|
|
1595
|
+
isOpen,
|
|
1596
|
+
setIsOpen,
|
|
1597
|
+
activeTab,
|
|
1598
|
+
setActiveTab,
|
|
1599
|
+
overlayRootRef,
|
|
1600
|
+
chatEndRef,
|
|
1601
|
+
publishState,
|
|
1602
|
+
selectableModels: modelOptions,
|
|
1603
|
+
showModelPicker,
|
|
1604
|
+
modelId,
|
|
1605
|
+
setModelId,
|
|
1606
|
+
includeThinking,
|
|
1607
|
+
selectedElement,
|
|
1608
|
+
clearSelectedElement,
|
|
1609
|
+
email,
|
|
1610
|
+
setEmail,
|
|
1611
|
+
password,
|
|
1612
|
+
setPassword,
|
|
1613
|
+
signingIn,
|
|
1614
|
+
signInWithPassword,
|
|
1615
|
+
message,
|
|
1616
|
+
setMessage,
|
|
1617
|
+
messages,
|
|
1618
|
+
sending,
|
|
1619
|
+
onSend,
|
|
1620
|
+
onPublish,
|
|
1621
|
+
onRollback,
|
|
1622
|
+
history,
|
|
1623
|
+
deletingCheckpointId,
|
|
1624
|
+
onDeleteCheckpoint,
|
|
1625
|
+
onClearChat,
|
|
1626
|
+
onMessageKeyDown,
|
|
1627
|
+
assistantAvatarFallbackLabel,
|
|
1628
|
+
showAssistantAvatarImage,
|
|
1629
|
+
assistantAvatarUrl: config.assistantAvatarUrl,
|
|
1630
|
+
setAssistantAvatarFailed,
|
|
1631
|
+
clearChatDisabled: sending || messages.length === 0 && !message.trim() && !selectedElement
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
// src/overlay.tsx
|
|
1636
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1637
|
+
function WebmasterDroidOverlay() {
|
|
1638
|
+
const controller = useOverlayController();
|
|
1639
|
+
if (!controller.isAdminMode) {
|
|
1640
|
+
return null;
|
|
1641
|
+
}
|
|
1642
|
+
return /* @__PURE__ */ jsx4(Fragment2, { children: controller.isOpen ? /* @__PURE__ */ jsxs2(
|
|
1140
1643
|
"div",
|
|
1141
1644
|
{
|
|
1142
|
-
ref: overlayRootRef,
|
|
1645
|
+
ref: controller.overlayRootRef,
|
|
1143
1646
|
"data-admin-overlay-root": true,
|
|
1144
1647
|
className: "fixed bottom-4 right-4 z-[100] flex h-[62vh] w-[min(480px,calc(100vw-1.5rem))] flex-col overflow-hidden rounded-lg border border-stone-300 bg-[#f6f2eb] text-stone-900 shadow-2xl",
|
|
1145
|
-
style: {
|
|
1146
|
-
fontFamily: "var(--font-ibm-plex-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace"
|
|
1147
|
-
},
|
|
1648
|
+
style: { fontFamily: OVERLAY_FONT_FAMILY },
|
|
1148
1649
|
children: [
|
|
1149
|
-
/* @__PURE__ */
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
)
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
}
|
|
1212
|
-
) })
|
|
1213
|
-
}
|
|
1214
|
-
) : null,
|
|
1215
|
-
/* @__PURE__ */ jsx3(
|
|
1216
|
-
"button",
|
|
1217
|
-
{
|
|
1218
|
-
type: "button",
|
|
1219
|
-
className: "rounded border border-stone-300 px-2 py-1 text-[11px] leading-4 text-stone-700 hover:bg-[#efe8dc]",
|
|
1220
|
-
onClick: () => setIsOpen(false),
|
|
1221
|
-
children: "Close"
|
|
1222
|
-
}
|
|
1223
|
-
)
|
|
1224
|
-
] })
|
|
1225
|
-
] }) }),
|
|
1226
|
-
!isAuthenticated ? /* @__PURE__ */ jsx3("section", { className: "flex min-h-0 flex-1 items-center justify-center bg-[#ece7dd] p-3", children: !authConfigured ? /* @__PURE__ */ jsx3("div", { className: "w-full max-w-sm rounded border border-red-300 bg-[#f8f3e9] p-3 text-[11px] leading-4 text-red-700", children: "Missing Supabase config (`supabaseUrl` / `supabaseAnonKey`)." }) : /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded border border-stone-300 bg-[#f8f3e9] p-3", children: [
|
|
1227
|
-
/* @__PURE__ */ jsx3("h3", { className: "mb-2 text-[12px] font-semibold text-stone-700", children: "Sign in" }),
|
|
1228
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1229
|
-
/* @__PURE__ */ jsx3(
|
|
1230
|
-
"input",
|
|
1231
|
-
{
|
|
1232
|
-
type: "text",
|
|
1233
|
-
value: email,
|
|
1234
|
-
onChange: (event) => setEmail(event.target.value),
|
|
1235
|
-
placeholder: "login",
|
|
1236
|
-
className: "w-full rounded border border-stone-300 bg-[#f4efe6] px-2 py-1.5 text-[12px] text-stone-900 outline-none focus:border-stone-500"
|
|
1237
|
-
}
|
|
1238
|
-
),
|
|
1239
|
-
/* @__PURE__ */ jsx3(
|
|
1240
|
-
"input",
|
|
1241
|
-
{
|
|
1242
|
-
type: "password",
|
|
1243
|
-
value: password,
|
|
1244
|
-
onChange: (event) => setPassword(event.target.value),
|
|
1245
|
-
placeholder: "Password",
|
|
1246
|
-
className: "w-full rounded border border-stone-300 bg-[#f4efe6] px-2 py-1.5 text-[12px] text-stone-900 outline-none focus:border-stone-500"
|
|
1247
|
-
}
|
|
1248
|
-
),
|
|
1249
|
-
/* @__PURE__ */ jsx3(
|
|
1250
|
-
"button",
|
|
1251
|
-
{
|
|
1252
|
-
type: "button",
|
|
1253
|
-
onClick: signInWithPassword,
|
|
1254
|
-
disabled: signingIn || !email.trim() || !password,
|
|
1255
|
-
className: "w-full rounded border border-stone-700 bg-stone-800 px-2 py-1.5 text-[12px] font-medium text-stone-100 hover:bg-stone-700 disabled:cursor-not-allowed disabled:opacity-50",
|
|
1256
|
-
children: signingIn ? "Signing in" : "Sign in"
|
|
1257
|
-
}
|
|
1258
|
-
)
|
|
1259
|
-
] })
|
|
1260
|
-
] }) }) : activeTab === "chat" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1261
|
-
/* @__PURE__ */ jsxs("section", { className: "flex-1 space-y-1 overflow-auto bg-[#ece7dd] p-2", children: [
|
|
1262
|
-
messages.map((entry) => {
|
|
1263
|
-
const isAssistant = entry.role === "assistant";
|
|
1264
|
-
const isPendingAssistant = isAssistant && entry.status === "pending";
|
|
1265
|
-
return /* @__PURE__ */ jsx3(
|
|
1266
|
-
"div",
|
|
1267
|
-
{
|
|
1268
|
-
className: entry.role === "tool" ? "max-w-[96%] px-0.5 py-0 text-[10px] leading-tight text-stone-500" : `max-w-[92%] rounded-md py-1.5 text-[12px] leading-4 ${entry.role === "user" ? "ml-auto bg-[#2e2b27] px-2 text-stone-50" : entry.role === "thinking" ? "bg-[#e3dbce] px-2 text-stone-700" : isAssistant ? "relative border border-[#d6ccbb] bg-[#f8f3e9] pl-8 pr-2 text-stone-800" : "bg-[#ddd2bf] px-2 text-stone-800"}`,
|
|
1269
|
-
children: entry.role === "tool" ? /* @__PURE__ */ jsx3("span", { children: entry.text }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1270
|
-
isAssistant ? showAssistantAvatarImage ? /* @__PURE__ */ jsx3(
|
|
1271
|
-
"img",
|
|
1272
|
-
{
|
|
1273
|
-
src: config.assistantAvatarUrl,
|
|
1274
|
-
alt: "",
|
|
1275
|
-
"aria-hidden": "true",
|
|
1276
|
-
className: `pointer-events-none absolute left-2 top-1.5 h-[18px] w-[18px] select-none rounded-full border border-[#d6ccbb] bg-[#efe8dc] object-cover ${isPendingAssistant ? "animate-pulse" : ""}`,
|
|
1277
|
-
onError: () => setAssistantAvatarFailed(true)
|
|
1278
|
-
}
|
|
1279
|
-
) : /* @__PURE__ */ jsx3(
|
|
1280
|
-
"span",
|
|
1281
|
-
{
|
|
1282
|
-
"aria-hidden": "true",
|
|
1283
|
-
className: `pointer-events-none absolute left-2 top-1.5 inline-flex h-[18px] w-[18px] select-none items-center justify-center rounded-full border border-[#d6ccbb] bg-[#efe8dc] text-[9px] font-semibold text-stone-700 ${isPendingAssistant ? "animate-pulse" : ""}`,
|
|
1284
|
-
children: assistantAvatarFallbackLabel
|
|
1285
|
-
}
|
|
1286
|
-
) : null,
|
|
1287
|
-
/* @__PURE__ */ jsx3("div", { className: "max-w-none text-inherit [&_code]:rounded [&_code]:bg-stone-900/10 [&_code]:px-1 [&_ol]:list-decimal [&_ol]:pl-4 [&_p]:mb-1 [&_p:last-child]:mb-0 [&_ul]:list-disc [&_ul]:pl-4", children: isPendingAssistant && !entry.text.trim() ? /* @__PURE__ */ jsx3("span", { className: "block h-4", "aria-hidden": "true" }) : /* @__PURE__ */ jsx3(ReactMarkdown, { remarkPlugins: [remarkGfm], children: entry.text }) })
|
|
1288
|
-
] })
|
|
1289
|
-
},
|
|
1290
|
-
entry.id
|
|
1650
|
+
/* @__PURE__ */ jsx4(
|
|
1651
|
+
OverlayHeader,
|
|
1652
|
+
{
|
|
1653
|
+
isAuthenticated: controller.isAuthenticated,
|
|
1654
|
+
publishState: controller.publishState,
|
|
1655
|
+
activeTab: controller.activeTab,
|
|
1656
|
+
historyCount: controller.history.published.length + controller.history.checkpoints.length,
|
|
1657
|
+
clearChatDisabled: controller.clearChatDisabled,
|
|
1658
|
+
onPublish: () => {
|
|
1659
|
+
void controller.onPublish();
|
|
1660
|
+
},
|
|
1661
|
+
onTabChange: controller.setActiveTab,
|
|
1662
|
+
onClearChat: controller.onClearChat,
|
|
1663
|
+
onClose: () => controller.setIsOpen(false)
|
|
1664
|
+
}
|
|
1665
|
+
),
|
|
1666
|
+
!controller.isAuthenticated ? /* @__PURE__ */ jsx4(
|
|
1667
|
+
OverlayLoginPanel,
|
|
1668
|
+
{
|
|
1669
|
+
authConfigured: controller.authConfigured,
|
|
1670
|
+
email: controller.email,
|
|
1671
|
+
password: controller.password,
|
|
1672
|
+
signingIn: controller.signingIn,
|
|
1673
|
+
onEmailChange: controller.setEmail,
|
|
1674
|
+
onPasswordChange: controller.setPassword,
|
|
1675
|
+
onSignIn: () => {
|
|
1676
|
+
void controller.signInWithPassword();
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
) : controller.activeTab === "chat" ? /* @__PURE__ */ jsx4(
|
|
1680
|
+
OverlayChatPanel,
|
|
1681
|
+
{
|
|
1682
|
+
messages: controller.messages,
|
|
1683
|
+
chatEndRef: controller.chatEndRef,
|
|
1684
|
+
showAssistantAvatarImage: controller.showAssistantAvatarImage,
|
|
1685
|
+
assistantAvatarUrl: controller.assistantAvatarUrl,
|
|
1686
|
+
assistantAvatarFallbackLabel: controller.assistantAvatarFallbackLabel,
|
|
1687
|
+
onAssistantAvatarError: () => controller.setAssistantAvatarFailed(true),
|
|
1688
|
+
showModelPicker: controller.showModelPicker,
|
|
1689
|
+
selectableModels: controller.selectableModels,
|
|
1690
|
+
modelId: controller.modelId,
|
|
1691
|
+
sending: controller.sending,
|
|
1692
|
+
onModelChange: controller.setModelId,
|
|
1693
|
+
selectedElement: controller.selectedElement,
|
|
1694
|
+
onClearSelectedElement: controller.clearSelectedElement,
|
|
1695
|
+
message: controller.message,
|
|
1696
|
+
onMessageChange: controller.setMessage,
|
|
1697
|
+
onMessageKeyDown: controller.onMessageKeyDown,
|
|
1698
|
+
onSend: () => {
|
|
1699
|
+
void controller.onSend();
|
|
1700
|
+
},
|
|
1701
|
+
isAuthenticated: controller.isAuthenticated
|
|
1702
|
+
}
|
|
1703
|
+
) : /* @__PURE__ */ jsx4(
|
|
1704
|
+
OverlayHistoryPanel,
|
|
1705
|
+
{
|
|
1706
|
+
history: controller.history,
|
|
1707
|
+
deletingCheckpointId: controller.deletingCheckpointId,
|
|
1708
|
+
onRestorePublished: (item) => {
|
|
1709
|
+
void controller.onRollback(
|
|
1710
|
+
{ sourceType: "published", sourceId: item.id },
|
|
1711
|
+
`published snapshot at ${formatHistoryTime(item.createdAt)}`
|
|
1291
1712
|
);
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
),
|
|
1305
|
-
/* @__PURE__ */ jsx3(
|
|
1306
|
-
"select",
|
|
1307
|
-
{
|
|
1308
|
-
id: "admin-model-picker",
|
|
1309
|
-
value: modelId ?? selectableModels[0]?.id,
|
|
1310
|
-
onChange: (event) => setModelId(event.target.value),
|
|
1311
|
-
disabled: sending,
|
|
1312
|
-
className: "h-7 min-w-0 flex-1 rounded border border-stone-300 bg-[#f7f2e8] px-2 text-[11px] text-stone-800 outline-none focus:border-stone-500 disabled:cursor-not-allowed disabled:opacity-60",
|
|
1313
|
-
children: selectableModels.map((option) => /* @__PURE__ */ jsx3("option", { value: option.id, children: option.label }, option.id))
|
|
1314
|
-
}
|
|
1315
|
-
)
|
|
1316
|
-
] }) : null,
|
|
1317
|
-
selectedElement ? /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center gap-1 rounded border border-stone-300 bg-[#e8dfd1] px-1.5 py-1", children: [
|
|
1318
|
-
/* @__PURE__ */ jsx3("span", { className: "inline-flex shrink-0 items-center justify-center rounded border border-stone-300 bg-[#f7f2e8] px-1 py-0.5 text-[9px] font-semibold text-stone-700", children: kindIcon(selectedElement.kind) }),
|
|
1319
|
-
/* @__PURE__ */ jsxs("p", { className: "min-w-0 flex-1 truncate text-[10px] leading-3.5 text-stone-600", children: [
|
|
1320
|
-
/* @__PURE__ */ jsx3("span", { className: "font-semibold text-stone-800", children: selectedElement.label }),
|
|
1321
|
-
/* @__PURE__ */ jsxs("span", { children: [
|
|
1322
|
-
" \xB7 ",
|
|
1323
|
-
selectedElement.path
|
|
1324
|
-
] }),
|
|
1325
|
-
selectedElement.preview ? /* @__PURE__ */ jsxs("span", { children: [
|
|
1326
|
-
" \xB7 ",
|
|
1327
|
-
selectedElement.preview
|
|
1328
|
-
] }) : null
|
|
1329
|
-
] }),
|
|
1330
|
-
/* @__PURE__ */ jsx3(
|
|
1331
|
-
"button",
|
|
1332
|
-
{
|
|
1333
|
-
type: "button",
|
|
1334
|
-
"aria-label": "Clear selected element",
|
|
1335
|
-
title: "Clear selected element",
|
|
1336
|
-
className: "inline-flex h-5 w-5 shrink-0 items-center justify-center rounded border border-stone-300 bg-[#f7f2e8] text-stone-700 hover:bg-[#efe8dc]",
|
|
1337
|
-
onClick: clearSelectedElement,
|
|
1338
|
-
children: /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 20 20", fill: "none", className: "h-3 w-3", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(
|
|
1339
|
-
"path",
|
|
1340
|
-
{
|
|
1341
|
-
d: "M5 5L15 15M15 5L5 15",
|
|
1342
|
-
stroke: "currentColor",
|
|
1343
|
-
strokeWidth: "1.5",
|
|
1344
|
-
strokeLinecap: "round"
|
|
1345
|
-
}
|
|
1346
|
-
) })
|
|
1347
|
-
}
|
|
1348
|
-
)
|
|
1349
|
-
] }) : null,
|
|
1350
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
|
|
1351
|
-
/* @__PURE__ */ jsx3(
|
|
1352
|
-
"textarea",
|
|
1353
|
-
{
|
|
1354
|
-
value: message,
|
|
1355
|
-
onChange: (event) => setMessage(event.target.value),
|
|
1356
|
-
onKeyDown: onMessageKeyDown,
|
|
1357
|
-
rows: 2,
|
|
1358
|
-
placeholder: "Ask the agent to edit text, image URLs, or theme tokens",
|
|
1359
|
-
className: "flex-1 resize-none rounded border border-stone-300 bg-[#f4efe6] px-2 py-1.5 text-[12px] leading-4 text-stone-900 outline-none placeholder:text-stone-500 focus:border-stone-500"
|
|
1360
|
-
}
|
|
1361
|
-
),
|
|
1362
|
-
/* @__PURE__ */ jsx3(
|
|
1363
|
-
"button",
|
|
1364
|
-
{
|
|
1365
|
-
type: "button",
|
|
1366
|
-
onClick: onSend,
|
|
1367
|
-
disabled: !isAuthenticated || sending || !message.trim(),
|
|
1368
|
-
className: "rounded border border-stone-500 bg-stone-600 px-3 py-1.5 text-[12px] font-semibold text-stone-100 hover:bg-stone-700 disabled:cursor-not-allowed disabled:opacity-50",
|
|
1369
|
-
children: sending ? "Sending" : "Send"
|
|
1370
|
-
}
|
|
1371
|
-
)
|
|
1372
|
-
] })
|
|
1373
|
-
] })
|
|
1374
|
-
] }) : /* @__PURE__ */ jsx3("section", { className: "flex min-h-0 flex-1 flex-col p-2 text-[11px] leading-4", children: /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-2 overflow-hidden", children: [
|
|
1375
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded border border-stone-300 bg-[#f8f3e9]", children: [
|
|
1376
|
-
/* @__PURE__ */ jsxs("div", { className: "border-b border-stone-200 px-2 py-1 font-semibold text-stone-700", children: [
|
|
1377
|
-
"Published (",
|
|
1378
|
-
history.published.length,
|
|
1379
|
-
")"
|
|
1380
|
-
] }),
|
|
1381
|
-
/* @__PURE__ */ jsx3("div", { className: "max-h-40 overflow-auto px-2 py-1.5", children: history.published.length > 0 ? /* @__PURE__ */ jsx3("div", { className: "space-y-1", children: history.published.map((item) => /* @__PURE__ */ jsxs(
|
|
1382
|
-
"div",
|
|
1383
|
-
{
|
|
1384
|
-
className: "flex items-center justify-between gap-2 rounded border border-stone-200 bg-[#f2ecdf] px-2 py-1",
|
|
1385
|
-
children: [
|
|
1386
|
-
/* @__PURE__ */ jsx3("span", { className: "truncate text-[10px] text-stone-700", children: formatHistoryTime(item.createdAt) }),
|
|
1387
|
-
/* @__PURE__ */ jsx3(
|
|
1388
|
-
"button",
|
|
1389
|
-
{
|
|
1390
|
-
type: "button",
|
|
1391
|
-
className: "rounded border border-stone-300 bg-[#f7f2e8] px-1.5 py-0.5 text-[10px] text-stone-700 hover:bg-[#efe8dc]",
|
|
1392
|
-
onClick: () => onRollback(
|
|
1393
|
-
{ sourceType: "published", sourceId: item.id },
|
|
1394
|
-
`published snapshot at ${formatHistoryTime(item.createdAt)}`
|
|
1395
|
-
),
|
|
1396
|
-
children: "Restore"
|
|
1397
|
-
}
|
|
1398
|
-
)
|
|
1399
|
-
]
|
|
1400
|
-
},
|
|
1401
|
-
`pub-${item.id}`
|
|
1402
|
-
)) }) : /* @__PURE__ */ jsx3("p", { className: "text-[10px] text-stone-500", children: "No published snapshots." }) })
|
|
1403
|
-
] }),
|
|
1404
|
-
/* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col rounded border border-stone-300 bg-[#f8f3e9]", children: [
|
|
1405
|
-
/* @__PURE__ */ jsxs("div", { className: "border-b border-stone-200 px-2 py-1 font-semibold text-stone-700", children: [
|
|
1406
|
-
"Checkpoints (",
|
|
1407
|
-
history.checkpoints.length,
|
|
1408
|
-
")"
|
|
1409
|
-
] }),
|
|
1410
|
-
/* @__PURE__ */ jsx3("div", { className: "min-h-0 flex-1 overflow-auto px-2 py-1.5", children: history.checkpoints.length > 0 ? /* @__PURE__ */ jsx3("div", { className: "space-y-1", children: history.checkpoints.map((item) => /* @__PURE__ */ jsxs(
|
|
1411
|
-
"div",
|
|
1412
|
-
{
|
|
1413
|
-
className: "flex items-start justify-between gap-2 rounded border border-stone-200 bg-[#f2ecdf] px-2 py-1",
|
|
1414
|
-
children: [
|
|
1415
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1416
|
-
/* @__PURE__ */ jsx3("p", { className: "truncate text-[10px] text-stone-700", children: formatHistoryTime(item.createdAt) }),
|
|
1417
|
-
item.reason ? /* @__PURE__ */ jsx3("p", { className: "truncate text-[10px] text-stone-500", children: item.reason }) : null
|
|
1418
|
-
] }),
|
|
1419
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
1420
|
-
/* @__PURE__ */ jsx3(
|
|
1421
|
-
"button",
|
|
1422
|
-
{
|
|
1423
|
-
type: "button",
|
|
1424
|
-
disabled: deletingCheckpointId === item.id,
|
|
1425
|
-
className: "rounded border border-stone-300 bg-[#f7f2e8] px-1.5 py-0.5 text-[10px] text-stone-700 hover:bg-[#efe8dc] disabled:cursor-not-allowed disabled:opacity-50",
|
|
1426
|
-
onClick: () => onRollback(
|
|
1427
|
-
{ sourceType: "checkpoint", sourceId: item.id },
|
|
1428
|
-
`checkpoint at ${formatHistoryTime(item.createdAt)}`
|
|
1429
|
-
),
|
|
1430
|
-
children: "Restore"
|
|
1431
|
-
}
|
|
1432
|
-
),
|
|
1433
|
-
/* @__PURE__ */ jsx3(
|
|
1434
|
-
"button",
|
|
1435
|
-
{
|
|
1436
|
-
type: "button",
|
|
1437
|
-
"aria-label": "Delete checkpoint",
|
|
1438
|
-
title: "Delete checkpoint",
|
|
1439
|
-
disabled: deletingCheckpointId === item.id,
|
|
1440
|
-
className: "inline-flex h-6 w-6 items-center justify-center rounded border border-stone-300 bg-[#f7f2e8] text-stone-700 hover:bg-[#efe8dc] disabled:cursor-not-allowed disabled:opacity-50",
|
|
1441
|
-
onClick: () => {
|
|
1442
|
-
void onDeleteCheckpoint(item);
|
|
1443
|
-
},
|
|
1444
|
-
children: /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 20 20", fill: "none", className: "h-3.5 w-3.5", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(
|
|
1445
|
-
"path",
|
|
1446
|
-
{
|
|
1447
|
-
d: "M4.5 5.5H15.5M8 3.75H12M7 7.5V13.5M10 7.5V13.5M13 7.5V13.5M6.5 5.5L7 15C7.03 15.6 7.53 16.08 8.13 16.08H11.87C12.47 16.08 12.97 15.6 13 15L13.5 5.5",
|
|
1448
|
-
stroke: "currentColor",
|
|
1449
|
-
strokeWidth: "1.4",
|
|
1450
|
-
strokeLinecap: "round",
|
|
1451
|
-
strokeLinejoin: "round"
|
|
1452
|
-
}
|
|
1453
|
-
) })
|
|
1454
|
-
}
|
|
1455
|
-
)
|
|
1456
|
-
] })
|
|
1457
|
-
]
|
|
1458
|
-
},
|
|
1459
|
-
`cp-${item.id}`
|
|
1460
|
-
)) }) : /* @__PURE__ */ jsx3("p", { className: "text-[10px] text-stone-500", children: "No checkpoints yet." }) })
|
|
1461
|
-
] })
|
|
1462
|
-
] }) })
|
|
1713
|
+
},
|
|
1714
|
+
onRestoreCheckpoint: (item) => {
|
|
1715
|
+
void controller.onRollback(
|
|
1716
|
+
{ sourceType: "checkpoint", sourceId: item.id },
|
|
1717
|
+
`checkpoint at ${formatHistoryTime(item.createdAt)}`
|
|
1718
|
+
);
|
|
1719
|
+
},
|
|
1720
|
+
onDeleteCheckpoint: (item) => {
|
|
1721
|
+
void controller.onDeleteCheckpoint(item);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
)
|
|
1463
1725
|
]
|
|
1464
1726
|
}
|
|
1465
|
-
) : /* @__PURE__ */
|
|
1466
|
-
"button",
|
|
1467
|
-
{
|
|
1468
|
-
type: "button",
|
|
1469
|
-
onClick: () => setIsOpen(true),
|
|
1470
|
-
className: "fixed bottom-4 right-4 z-[100] rounded-full border border-stone-600 bg-stone-700 px-4 py-2 text-[12px] font-semibold text-stone-100 shadow-xl hover:bg-stone-800",
|
|
1471
|
-
style: {
|
|
1472
|
-
fontFamily: "var(--font-ibm-plex-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace"
|
|
1473
|
-
},
|
|
1474
|
-
children: "Chat to Webmaster"
|
|
1475
|
-
}
|
|
1476
|
-
) });
|
|
1727
|
+
) : /* @__PURE__ */ jsx4(OverlayLauncherButton, { onOpen: () => controller.setIsOpen(true) }) });
|
|
1477
1728
|
}
|
|
1478
1729
|
|
|
1479
1730
|
// src/runtime.tsx
|
|
@@ -1484,7 +1735,10 @@ import {
|
|
|
1484
1735
|
useMemo as useMemo3,
|
|
1485
1736
|
useState as useState3
|
|
1486
1737
|
} from "react";
|
|
1487
|
-
import {
|
|
1738
|
+
import {
|
|
1739
|
+
createDefaultCmsDocument
|
|
1740
|
+
} from "@webmaster-droid/contracts";
|
|
1741
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1488
1742
|
var CmsRuntimeContext = createContext3(null);
|
|
1489
1743
|
function createThemeCssVariables(tokens) {
|
|
1490
1744
|
return {
|
|
@@ -1499,6 +1753,10 @@ function createThemeCssVariables(tokens) {
|
|
|
1499
1753
|
}
|
|
1500
1754
|
function CmsRuntimeBridge(props) {
|
|
1501
1755
|
const { config, isAdminMode, isAuthenticated, token, refreshKey } = useWebmasterDroid();
|
|
1756
|
+
const defaultDocument = useMemo3(
|
|
1757
|
+
() => props.fallbackDocument ?? createDefaultCmsDocument(),
|
|
1758
|
+
[props.fallbackDocument]
|
|
1759
|
+
);
|
|
1502
1760
|
const stage = useMemo3(
|
|
1503
1761
|
() => isAdminMode && isAuthenticated ? "draft" : "live",
|
|
1504
1762
|
[isAdminMode, isAuthenticated]
|
|
@@ -1509,7 +1767,7 @@ function CmsRuntimeBridge(props) {
|
|
|
1509
1767
|
);
|
|
1510
1768
|
const [state, setState] = useState3({
|
|
1511
1769
|
requestKey: "",
|
|
1512
|
-
document:
|
|
1770
|
+
document: defaultDocument,
|
|
1513
1771
|
error: null
|
|
1514
1772
|
});
|
|
1515
1773
|
useEffect3(() => {
|
|
@@ -1530,14 +1788,14 @@ function CmsRuntimeBridge(props) {
|
|
|
1530
1788
|
const message = error2 instanceof Error ? error2.message : "Failed to load content.";
|
|
1531
1789
|
setState({
|
|
1532
1790
|
requestKey,
|
|
1533
|
-
document:
|
|
1791
|
+
document: defaultDocument,
|
|
1534
1792
|
error: message
|
|
1535
1793
|
});
|
|
1536
1794
|
});
|
|
1537
1795
|
return () => {
|
|
1538
1796
|
ignore = true;
|
|
1539
1797
|
};
|
|
1540
|
-
}, [config.apiBaseUrl,
|
|
1798
|
+
}, [config.apiBaseUrl, defaultDocument, requestKey, stage, token]);
|
|
1541
1799
|
const loading = state.requestKey !== requestKey;
|
|
1542
1800
|
const error = loading ? null : state.error;
|
|
1543
1801
|
const value = useMemo3(
|
|
@@ -1549,14 +1807,14 @@ function CmsRuntimeBridge(props) {
|
|
|
1549
1807
|
}),
|
|
1550
1808
|
[error, loading, stage, state.document]
|
|
1551
1809
|
);
|
|
1552
|
-
const content = props.applyThemeTokens ? /* @__PURE__ */
|
|
1553
|
-
return /* @__PURE__ */
|
|
1554
|
-
/* @__PURE__ */
|
|
1555
|
-
props.includeOverlay ? /* @__PURE__ */
|
|
1810
|
+
const content = props.applyThemeTokens ? /* @__PURE__ */ jsx5("div", { style: createThemeCssVariables(value.document.themeTokens), children: props.children }) : props.children;
|
|
1811
|
+
return /* @__PURE__ */ jsxs3(CmsRuntimeContext.Provider, { value, children: [
|
|
1812
|
+
/* @__PURE__ */ jsx5(EditableProvider, { document: value.document, mode: stage, enabled: isAdminMode, children: content }),
|
|
1813
|
+
props.includeOverlay ? /* @__PURE__ */ jsx5(WebmasterDroidOverlay, {}) : null
|
|
1556
1814
|
] });
|
|
1557
1815
|
}
|
|
1558
1816
|
function WebmasterDroidRuntime(props) {
|
|
1559
|
-
return /* @__PURE__ */
|
|
1817
|
+
return /* @__PURE__ */ jsx5(WebmasterDroidProvider, { config: props.config, children: /* @__PURE__ */ jsx5(
|
|
1560
1818
|
CmsRuntimeBridge,
|
|
1561
1819
|
{
|
|
1562
1820
|
fallbackDocument: props.fallbackDocument,
|