camox 0.62.0 → 0.65.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/dist/core/createApp.d.ts +28 -28
- package/dist/core/createLayout.d.ts +1 -1
- package/dist/core/derivedRoutes.js +3 -30
- package/dist/core/editing/createEditableBlock.d.ts +604 -604
- package/dist/features/canvas/canvasCamera.js +11 -2
- package/dist/features/canvas/useCanvasCamera.js +14 -10
- package/dist/features/page/CuratedPageContent.js +2 -1
- package/dist/features/page/UnplacedBlockContent.js +52 -0
- package/dist/features/preview/components/PreviewPanel.js +40 -12
- package/dist/features/preview/components/SidebarStack.js +78 -9
- package/dist/features/preview/strayCaretGuard.js +34 -0
- package/dist/features/provider/CoreCamoxProvider.js +5 -3
- package/dist/features/routes/pageRuntime.d.ts +2 -0
- package/dist/features/routes/pageRuntime.js +8 -5
- package/dist/features/runtime/pageApp.js +111 -74
- package/dist/features/runtime/pageClient.js +22 -2
- package/dist/features/runtime/runtime.d.ts +10 -0
- package/dist/features/runtime/runtime.js +314 -4
- package/dist/features/runtime/runtimeRoutes.d.ts +5 -0
- package/dist/features/runtime/runtimeRoutes.js +5 -0
- package/dist/features/studio/components/ChatWidget.js +88 -62
- package/dist/features/studio/components/ConnectAgent.js +313 -0
- package/dist/features/studio/components/Patrol.js +455 -205
- package/dist/features/studio/components/ReviewDraftSheet.js +431 -334
- package/dist/features/studio/components/Squareling.js +74 -41
- package/dist/features/studio/components/ThreadComposer.js +130 -107
- package/dist/features/studio/components/ThreadMessages.js +136 -30
- package/dist/features/studio/components/clientLogoPaths.js +10 -0
- package/dist/features/studio/findingSentence.js +37 -0
- package/dist/features/studio/reportSentence.js +15 -0
- package/dist/features/studio/studioStore.js +23 -1
- package/dist/features/studio/workingSentence.js +8 -0
- package/dist/lib/api-client.d.ts +1 -0
- package/dist/lib/auth.js +34 -32
- package/dist/lib/locale-path.js +1 -7
- package/dist/lib/queries.js +9 -1
- package/dist/lib/server-auth-cookie.js +17 -4
- package/dist/lib/studio-reads.d.ts +1 -1
- package/dist/lib/studio-reads.js +5 -1
- package/dist/package.js +1 -1
- package/dist/studio.css +1 -1
- package/package.json +16 -5
|
@@ -2,7 +2,7 @@ import { serializeJsonForHtml } from "../../lib/html.js";
|
|
|
2
2
|
import { createRequestContext } from "../../core/requestContext.js";
|
|
3
3
|
import { UNNAMED_DEFAULT_LOCALE, localizedPath, parseLocalePath } from "../../lib/locale-path.js";
|
|
4
4
|
import { draftNamedBy } from "../../lib/draft-scope.js";
|
|
5
|
-
import { buildClearServerAuthCookieHeader, buildServerAuthCookieHeader, getServerAuthCookieHeader } from "../../lib/server-auth-cookie.js";
|
|
5
|
+
import { buildClearServerAuthCookieHeader, buildServerAuthCookieHeader, getLookToken, getServerAuthCookieHeader } from "../../lib/server-auth-cookie.js";
|
|
6
6
|
import { getCookie, getSetCookie } from "../../lib/stored-auth-cookies.js";
|
|
7
7
|
import { ARTIFACTS_PATH_PREFIX, RUNTIME_ROUTE_PATHS, matchRuntimeOwnedRoute } from "./runtimeRoutes.js";
|
|
8
8
|
import { reads } from "../../lib/studio-reads.js";
|
|
@@ -13,7 +13,7 @@ import { STUDIO_THEME_SCRIPT } from "../studio/studioTheme.js";
|
|
|
13
13
|
import { DefaultPageImage, renderPageImage } from "../og/pageImage.js";
|
|
14
14
|
import { discoverLayoutPages, matchDerivedLayout } from "../../core/derivedRoutes.js";
|
|
15
15
|
import { buildLayoutContext, curatedLayoutContext } from "../../core/layoutContext.js";
|
|
16
|
-
import { buildCamoxPageHead, buildFaviconUrl, buildPageHead, buildPageMeta, createServerApiClient, createServerStudioCache, curatedMarkdownResponse, declarePage, isNotFoundError, loadCamoxPageForRequest, parseMarkdownPath, prefersMarkdown, readDraftOrLive, readPageMarkdown, readScopeLocales, renderedHref, resolveMemberProject, withFrontMatter } from "../routes/pageRuntime.js";
|
|
16
|
+
import { buildCamoxPageHead, buildCamoxPageLoaderData, buildFaviconUrl, buildPageHead, buildPageMeta, createServerApiClient, createServerStudioCache, curatedMarkdownResponse, declarePage, isAuthSessionError, isNotFoundError, loadCamoxPage, loadCamoxPageForRequest, parseMarkdownPath, prefersMarkdown, readDraftOrLive, readPageMarkdown, readScopeLocales, renderedHref, resolveMemberProject, withFrontMatter } from "../routes/pageRuntime.js";
|
|
17
17
|
import { assetPrefix } from "./assetPrefix.js";
|
|
18
18
|
import { documentGoogleFonts } from "../og/documentFonts.js";
|
|
19
19
|
import { alternatesByPage, findAlternates, hreflangLinks } from "./alternates.js";
|
|
@@ -27,7 +27,7 @@ import { getPageBlockTypes } from "./pageBlockTypes.js";
|
|
|
27
27
|
import { withPageUrls } from "./structuredData.js";
|
|
28
28
|
import { curatedPageDescription, pageRecords, placedBlockNodes, withStructuredData } from "./pageGraph.js";
|
|
29
29
|
import { createProjectLogoResponse, projectLogo } from "./projectLogo.js";
|
|
30
|
-
import { LOCALE_HEADER } from "@camox/api-contract/drafts";
|
|
30
|
+
import { DRAFT_SEARCH_PARAM, LOCALE_HEADER, LOOK_BLOCK_DEFINITION_PARAM, LOOK_CONTENT_PARAM, LOOK_LOCALE_PARAM, LOOK_PATH_PARAM, LOOK_SETTINGS_PARAM, UNPLACED_BLOCK_ID } from "@camox/api-contract/drafts";
|
|
31
31
|
import { QueryClient, dehydrate } from "@tanstack/react-query";
|
|
32
32
|
import { createHead, renderSSRHead } from "@unhead/react/server";
|
|
33
33
|
|
|
@@ -503,6 +503,314 @@ function createRendererDocumentResponse(options) {
|
|
|
503
503
|
"X-Robots-Tag": "noindex"
|
|
504
504
|
} });
|
|
505
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Marks the look document ready (`data-camox-ready` on its root element) for the browser to wait
|
|
508
|
+
* on, once the page's client hydrated it (`camox:hydrated`), the window loaded, and every frame
|
|
509
|
+
* on the page, its Artifacts' included, loaded or failed to, then a frame later, once painted.
|
|
510
|
+
* Artifacts' frames load lazily, and their documents may fetch libraries as they start, so the
|
|
511
|
+
* window's `load` alone comes before them: the frames are made eager once hydrated, so those
|
|
512
|
+
* below the first screen load too, and each frame's own `load` is awaited. A frame that never
|
|
513
|
+
* loads leaves the document unready, and the capture waiting on it times out.
|
|
514
|
+
*/
|
|
515
|
+
const LOOK_READY_SCRIPT = `(()=>{const loaded=new WeakSet();let hydrated=false,marked=false;const mark=()=>{if(marked||!hydrated||document.readyState!=="complete")return;const frames=[...document.querySelectorAll("iframe")];for(const frame of frames)if(frame.getAttribute("loading")==="lazy")frame.setAttribute("loading","eager");if(!frames.every((frame)=>loaded.has(frame)))return;marked=true;requestAnimationFrame(()=>requestAnimationFrame(()=>document.documentElement.setAttribute("data-camox-ready","")))};const settle=(event)=>{if(event.target&&event.target.tagName==="IFRAME")loaded.add(event.target);mark()};document.addEventListener("load",settle,true);document.addEventListener("error",settle,true);addEventListener("load",mark,{once:true});addEventListener("camox:hydrated",()=>{hydrated=true;mark()},{once:true})})()`;
|
|
516
|
+
/** The headers every look document answer carries: private, never cached, never indexed. */
|
|
517
|
+
const LOOK_HEADERS = {
|
|
518
|
+
"Cache-Control": "private, no-store",
|
|
519
|
+
"X-Robots-Tag": "noindex"
|
|
520
|
+
};
|
|
521
|
+
/** What the look document answers when it shows nothing: no token, or one the API refused. */
|
|
522
|
+
function lookNotFound() {
|
|
523
|
+
return new Response("Not found", {
|
|
524
|
+
status: 404,
|
|
525
|
+
headers: LOOK_HEADERS
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
/** What the look document answers when the API refuses what it was asked to show, and why. */
|
|
529
|
+
function lookRefused(message) {
|
|
530
|
+
return new Response(message, {
|
|
531
|
+
status: 422,
|
|
532
|
+
headers: {
|
|
533
|
+
...LOOK_HEADERS,
|
|
534
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
function overrideParam(searchParams, name) {
|
|
539
|
+
const raw = searchParams.get(name);
|
|
540
|
+
if (raw === null) return {
|
|
541
|
+
ok: true,
|
|
542
|
+
value: void 0
|
|
543
|
+
};
|
|
544
|
+
try {
|
|
545
|
+
return {
|
|
546
|
+
ok: true,
|
|
547
|
+
value: JSON.parse(raw)
|
|
548
|
+
};
|
|
549
|
+
} catch {
|
|
550
|
+
return {
|
|
551
|
+
ok: false,
|
|
552
|
+
error: `The ${name} override isn't JSON.`
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* The lightweight document the agent's `look` tool captures (ADR 0032), for the draft
|
|
558
|
+
* `camox-draft` names, read with the look token the request's cookie carries, in the Locale
|
|
559
|
+
* `locale` names by its tag (the Default Locale without one). It shows the page at `path` as a
|
|
560
|
+
* visitor would, or in its Block Definition mode the block of the Block Definition
|
|
561
|
+
* `blockDefinition` names, placed nowhere, alone in the site's shell, with the `content` and
|
|
562
|
+
* `settings` overrides merged on. Either way it has the site's head and stylesheets and the
|
|
563
|
+
* page's client to hydrate its blocks, but nothing of the studio. It answers nothing without a
|
|
564
|
+
* token the API accepts.
|
|
565
|
+
*/
|
|
566
|
+
async function createLookDocumentResponse(request, options) {
|
|
567
|
+
const { apiUrl, authenticationUrl, projectUuid, renderPage } = options;
|
|
568
|
+
const { searchParams } = new URL(request.url);
|
|
569
|
+
const lookToken = getLookToken(request.headers);
|
|
570
|
+
const draftId = searchParams.get(DRAFT_SEARCH_PARAM) ?? void 0;
|
|
571
|
+
if (!lookToken || !draftId || !apiUrl || !authenticationUrl || !projectUuid || !renderPage) return lookNotFound();
|
|
572
|
+
const tag = searchParams.get(LOOK_LOCALE_PARAM)?.toLowerCase();
|
|
573
|
+
const blockDefinition = searchParams.get(LOOK_BLOCK_DEFINITION_PARAM);
|
|
574
|
+
const queryClient = new QueryClient();
|
|
575
|
+
let locales;
|
|
576
|
+
let locale;
|
|
577
|
+
let shown;
|
|
578
|
+
try {
|
|
579
|
+
locales = await createServerApiClient(apiUrl, {
|
|
580
|
+
lookToken,
|
|
581
|
+
draftId
|
|
582
|
+
}).locales.list({ projectUuid });
|
|
583
|
+
locale = locales.find((each) => tag ? each.tag.toLowerCase() === tag : each.isDefault);
|
|
584
|
+
if (!locale) return lookNotFound();
|
|
585
|
+
const reading = {
|
|
586
|
+
apiUrl,
|
|
587
|
+
lookToken,
|
|
588
|
+
draftId,
|
|
589
|
+
locale,
|
|
590
|
+
projectUuid,
|
|
591
|
+
queryClient
|
|
592
|
+
};
|
|
593
|
+
shown = blockDefinition ? await lookAtBlockDefinition(request, options, reading, blockDefinition) : await lookAtPage(request, options, reading);
|
|
594
|
+
} catch (error) {
|
|
595
|
+
if (isAuthSessionError(error) || isNotFoundError(error)) return lookNotFound();
|
|
596
|
+
throw error;
|
|
597
|
+
}
|
|
598
|
+
if (shown instanceof Response) return shown;
|
|
599
|
+
const document = await options.getDocument?.() ?? {};
|
|
600
|
+
const dehydratedState = dehydrate(queryClient);
|
|
601
|
+
const input = {
|
|
602
|
+
...shown,
|
|
603
|
+
look: true,
|
|
604
|
+
presentation: "public",
|
|
605
|
+
apiUrl,
|
|
606
|
+
authenticationUrl,
|
|
607
|
+
dehydratedState,
|
|
608
|
+
draftId,
|
|
609
|
+
projectUuid,
|
|
610
|
+
runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath),
|
|
611
|
+
locale,
|
|
612
|
+
locales
|
|
613
|
+
};
|
|
614
|
+
const appHtml = await renderPage(input);
|
|
615
|
+
const renderedHead = renderHead(document, createPageHeadInput(shown.head), options.stylesheetUrl, void 0, locale);
|
|
616
|
+
return new Response(`<!doctype html>
|
|
617
|
+
<html${renderedHead.htmlAttrs}>
|
|
618
|
+
<head>
|
|
619
|
+
${renderedHead.headTags}
|
|
620
|
+
<meta name="robots" content="noindex">
|
|
621
|
+
<script>${LOOK_READY_SCRIPT}<\/script>
|
|
622
|
+
${renderBlockPreloads(options, dehydratedState)}
|
|
623
|
+
<script id="__CAMOX_DATA__" type="application/json">${serializeJsonForHtml(hydrationInputFor(input, appHtml))}<\/script>
|
|
624
|
+
</head>
|
|
625
|
+
<body${renderedHead.bodyAttrs}>
|
|
626
|
+
${renderedHead.bodyTagsOpen}
|
|
627
|
+
<div id="root">${appHtml}</div>
|
|
628
|
+
${renderedHead.bodyTags}
|
|
629
|
+
${renderClientEntryScript(options.pageClientEntryUrl)}
|
|
630
|
+
</body>
|
|
631
|
+
</html>`, { headers: {
|
|
632
|
+
...LOOK_HEADERS,
|
|
633
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
634
|
+
"Referrer-Policy": "no-referrer"
|
|
635
|
+
} });
|
|
636
|
+
}
|
|
637
|
+
/** Where the look document's client believes it is: `path` of the draft, in its Locale. */
|
|
638
|
+
function lookHref(request, options, pathname, draftId) {
|
|
639
|
+
const href = new URL(withRuntimeBasePath(pathname, options.runtimeBasePath), request.url);
|
|
640
|
+
href.searchParams.set(DRAFT_SEARCH_PARAM, draftId);
|
|
641
|
+
return href;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* The look document's page at `path`, with its head, as a visitor would see it. A path is
|
|
645
|
+
* matched as the website serves it: a singleton layout's route first, then a curated page, then a
|
|
646
|
+
* derived layout's route.
|
|
647
|
+
*/
|
|
648
|
+
async function lookAtPage(request, options, reading) {
|
|
649
|
+
const { apiUrl, draftId, locale } = reading;
|
|
650
|
+
const path = normalizePagePath(new URL(request.url).searchParams.get(LOOK_PATH_PARAM));
|
|
651
|
+
const camoxApp = await options.getCamoxApp?.();
|
|
652
|
+
const match = camoxApp && matchDerivedLayout(camoxApp.getLayouts(), path);
|
|
653
|
+
if (match?.layout._internal.kind === "singleton") return lookAtLayoutPage(request, options, reading, path, match);
|
|
654
|
+
let page;
|
|
655
|
+
try {
|
|
656
|
+
page = await loadCamoxPage({
|
|
657
|
+
...reading,
|
|
658
|
+
pathname: path
|
|
659
|
+
});
|
|
660
|
+
} catch (error) {
|
|
661
|
+
if (match && isNotFoundError(error)) return lookAtLayoutPage(request, options, reading, path, match);
|
|
662
|
+
throw error;
|
|
663
|
+
}
|
|
664
|
+
const pathname = localizedPath(path, locale);
|
|
665
|
+
const href = lookHref(request, options, pathname, draftId);
|
|
666
|
+
const loaderData = buildCamoxPageLoaderData(apiUrl, page, href.origin);
|
|
667
|
+
return {
|
|
668
|
+
head: camoxApp ? buildCamoxPageHead(camoxApp, loaderData, locale) : {},
|
|
669
|
+
href: href.href,
|
|
670
|
+
layoutIdentity: getLayoutIdentity(page.layout),
|
|
671
|
+
loaderData,
|
|
672
|
+
pathname
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* The look document's page of a singleton or derived layout at `path`: the layout's blocks, read
|
|
677
|
+
* with the look token, around what its loader returns, its records read from the draft. A loader
|
|
678
|
+
* that finds nothing answers why.
|
|
679
|
+
*/
|
|
680
|
+
async function lookAtLayoutPage(request, options, reading, path, match) {
|
|
681
|
+
const { apiUrl, lookToken, draftId, locale, projectUuid, queryClient } = reading;
|
|
682
|
+
const app = await options.getCamoxApp?.();
|
|
683
|
+
const layout = match.layout._internal;
|
|
684
|
+
const pathname = localizedPath(path, locale);
|
|
685
|
+
let result;
|
|
686
|
+
let shared;
|
|
687
|
+
try {
|
|
688
|
+
[result, shared] = await Promise.all([loadLayoutResult(app, match, createLayoutRequestContext(options, "draft", {
|
|
689
|
+
lookToken,
|
|
690
|
+
draftId,
|
|
691
|
+
locale
|
|
692
|
+
}), locale), createServerApiClient(apiUrl, {
|
|
693
|
+
lookToken,
|
|
694
|
+
draftId,
|
|
695
|
+
locale
|
|
696
|
+
}).layouts.get({
|
|
697
|
+
projectUuid,
|
|
698
|
+
layoutId: layout.id
|
|
699
|
+
})]);
|
|
700
|
+
} catch (error) {
|
|
701
|
+
if (!isNotFoundError(error)) throw error;
|
|
702
|
+
return new Response(`The draft has no page at ${path}: the ${layout.id} layout's loader found nothing to show.`, {
|
|
703
|
+
status: 404,
|
|
704
|
+
headers: {
|
|
705
|
+
...LOOK_HEADERS,
|
|
706
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
const studioCache = createServerStudioCache(queryClient, {
|
|
711
|
+
apiUrl,
|
|
712
|
+
lookToken,
|
|
713
|
+
locale
|
|
714
|
+
});
|
|
715
|
+
const inDraft = { in: draftId };
|
|
716
|
+
const runtimeBasePath = normalizeRuntimeBasePath(options.runtimeBasePath);
|
|
717
|
+
studioCache.land(reads.layoutStructure({
|
|
718
|
+
projectUuid,
|
|
719
|
+
layoutId: layout.id
|
|
720
|
+
}), shared, inDraft);
|
|
721
|
+
studioCache.set(reads.derivedPageData({
|
|
722
|
+
projectUuid,
|
|
723
|
+
runtimeBasePath,
|
|
724
|
+
pathname
|
|
725
|
+
}), result ?? null, inDraft);
|
|
726
|
+
const href = lookHref(request, options, pathname, draftId);
|
|
727
|
+
const record = asCollectionItem(result);
|
|
728
|
+
const collection = record && app?.getCollectionById(record.collectionId);
|
|
729
|
+
const context = buildLayoutContext(layout, {
|
|
730
|
+
path: pathname,
|
|
731
|
+
locale: locale.tag,
|
|
732
|
+
projectName: shared.projectName,
|
|
733
|
+
data: result?.data,
|
|
734
|
+
params: match.params,
|
|
735
|
+
record: record && collection ? recordPageValues(collection, record) : void 0
|
|
736
|
+
});
|
|
737
|
+
return {
|
|
738
|
+
head: buildPageHead({
|
|
739
|
+
layout,
|
|
740
|
+
context,
|
|
741
|
+
origin: href.origin,
|
|
742
|
+
faviconUrl: buildFaviconUrl(apiUrl, shared.project),
|
|
743
|
+
hasMarkdown: layout.toMarkdown !== void 0,
|
|
744
|
+
recordType: record?.structuredData?.node?.["@type"]
|
|
745
|
+
}),
|
|
746
|
+
href: href.href,
|
|
747
|
+
layoutIdentity: getLayoutIdentity(shared.layout),
|
|
748
|
+
loaderData: null,
|
|
749
|
+
pathname,
|
|
750
|
+
derived: {
|
|
751
|
+
layoutId: layout.id,
|
|
752
|
+
page: {
|
|
753
|
+
params: match.params,
|
|
754
|
+
path: pathname,
|
|
755
|
+
projectName: shared.projectName
|
|
756
|
+
},
|
|
757
|
+
result,
|
|
758
|
+
layout: shared.layout
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* The look document's Block Definition mode: the block of the Block Definition `type` as the
|
|
764
|
+
* draft would show it placed, with its default content and Block Settings and the overrides the
|
|
765
|
+
* URL gives merged on, by the API, which checks them against its schemas. An override it refuses
|
|
766
|
+
* answers its message. The block is read as a page reads its blocks, so it renders and hydrates
|
|
767
|
+
* as a page's block does; its page is the Locale's home, which links resolve against.
|
|
768
|
+
*/
|
|
769
|
+
async function lookAtBlockDefinition(request, options, reading, type) {
|
|
770
|
+
const { apiUrl, lookToken, draftId, locale, projectUuid, queryClient } = reading;
|
|
771
|
+
const { searchParams } = new URL(request.url);
|
|
772
|
+
const content = overrideParam(searchParams, LOOK_CONTENT_PARAM);
|
|
773
|
+
const settings = overrideParam(searchParams, LOOK_SETTINGS_PARAM);
|
|
774
|
+
if (!content.ok) return lookRefused(content.error);
|
|
775
|
+
if (!settings.ok) return lookRefused(settings.error);
|
|
776
|
+
const studioCache = createServerStudioCache(queryClient, {
|
|
777
|
+
apiUrl,
|
|
778
|
+
lookToken,
|
|
779
|
+
locale
|
|
780
|
+
});
|
|
781
|
+
const inDraft = { in: draftId };
|
|
782
|
+
let bundle;
|
|
783
|
+
try {
|
|
784
|
+
[bundle] = await Promise.all([createServerApiClient(apiUrl, {
|
|
785
|
+
lookToken,
|
|
786
|
+
draftId,
|
|
787
|
+
locale
|
|
788
|
+
}).blocks.getUnplaced({
|
|
789
|
+
projectUuid,
|
|
790
|
+
type,
|
|
791
|
+
content: content.value,
|
|
792
|
+
settings: settings.value
|
|
793
|
+
}), studioCache.ensure(reads.pageList({ projectUuid }), inDraft).catch(() => null)]);
|
|
794
|
+
} catch (error) {
|
|
795
|
+
if (isBadRequestError(error)) return lookRefused(error.message);
|
|
796
|
+
throw error;
|
|
797
|
+
}
|
|
798
|
+
studioCache.set(reads.blockBundle(UNPLACED_BLOCK_ID), bundle, inDraft);
|
|
799
|
+
const pathname = localizedPath("/", locale);
|
|
800
|
+
return {
|
|
801
|
+
head: {},
|
|
802
|
+
href: lookHref(request, options, pathname, draftId).href,
|
|
803
|
+
layoutIdentity: null,
|
|
804
|
+
loaderData: null,
|
|
805
|
+
pathname,
|
|
806
|
+
lookBlock: UNPLACED_BLOCK_ID
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
/** Whether an API error refused the request's input, such as an override its schema refuses. */
|
|
810
|
+
function isBadRequestError(error) {
|
|
811
|
+
const { code, status } = error ?? {};
|
|
812
|
+
return code === "BAD_REQUEST" || status === 400;
|
|
813
|
+
}
|
|
506
814
|
function renderClientEntryScript(src) {
|
|
507
815
|
if (!src) return "";
|
|
508
816
|
return `<script type="module" src="${escapeXml(src)}"><\/script>`;
|
|
@@ -959,11 +1267,12 @@ function createLayoutRequestContext(options, source, reading) {
|
|
|
959
1267
|
* Reads the records `loader` and `routes` ask for, in `locale` or the default locale; none
|
|
960
1268
|
* without an API.
|
|
961
1269
|
*/
|
|
962
|
-
function createRecordLookup(options, source, { authCookieHeader, draftId, locale } = {}) {
|
|
1270
|
+
function createRecordLookup(options, source, { authCookieHeader, lookToken, draftId, locale } = {}) {
|
|
963
1271
|
const { apiUrl, projectUuid } = options;
|
|
964
1272
|
if (!apiUrl || !projectUuid) return void 0;
|
|
965
1273
|
const api = createServerApiClient(apiUrl, {
|
|
966
1274
|
authCookieHeader: source === "draft" ? authCookieHeader : void 0,
|
|
1275
|
+
lookToken: source === "draft" ? lookToken : void 0,
|
|
967
1276
|
draftId,
|
|
968
1277
|
locale
|
|
969
1278
|
});
|
|
@@ -1460,6 +1769,7 @@ async function handleCamoxRequest(request, options = {}) {
|
|
|
1460
1769
|
if (match.kind === "artifact") return createArtifactDocumentResponse(pathname, options);
|
|
1461
1770
|
if (match.kind === "og") return createOgResponse(request, options);
|
|
1462
1771
|
if (match.kind === "renderer") return createRendererDocumentResponse(options);
|
|
1772
|
+
if (match.kind === "look") return createLookDocumentResponse(request, options);
|
|
1463
1773
|
if (match.kind === "logo") return createProjectLogoResponse(request, options);
|
|
1464
1774
|
if (match.kind === "sitemap") return createSitemapResponse(request, options);
|
|
1465
1775
|
if (match.kind === "llms-txt") return createLlmsTxtResponse(request, options);
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
export declare const RUNTIME_ROUTE_PATHS: {
|
|
11
11
|
readonly data: "/_camox/data";
|
|
12
12
|
readonly health: "/_camox/health";
|
|
13
|
+
/**
|
|
14
|
+
* The lightweight document the agent's `look` tool captures (ADR 0032): one page of a draft,
|
|
15
|
+
* read with a look token, without the studio.
|
|
16
|
+
*/
|
|
17
|
+
readonly look: "/_camox/look";
|
|
13
18
|
readonly registry: "/_camox/registry";
|
|
14
19
|
/** The renderer frame's document (ADR 0023), which the studio loads to render its pages. */
|
|
15
20
|
readonly renderer: "/_camox/renderer";
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
const RUNTIME_ROUTE_PATHS = {
|
|
11
11
|
data: "/_camox/data",
|
|
12
12
|
health: "/_camox/health",
|
|
13
|
+
/**
|
|
14
|
+
* The lightweight document the agent's `look` tool captures (ADR 0032): one page of a draft,
|
|
15
|
+
* read with a look token, without the studio.
|
|
16
|
+
*/
|
|
17
|
+
look: "/_camox/look",
|
|
13
18
|
registry: "/_camox/registry",
|
|
14
19
|
/** The renderer frame's document (ADR 0023), which the studio loads to render its pages. */
|
|
15
20
|
renderer: "/_camox/renderer",
|
|
@@ -4,15 +4,18 @@ import { AuthContext } from "../../../lib/auth.js";
|
|
|
4
4
|
import { reads, useReadQuery } from "../../../lib/studio-reads.js";
|
|
5
5
|
import { cn } from "../../../lib/utils.js";
|
|
6
6
|
import { BOTTOM_CARD_EDGES, CARD_BODY_EDGES } from "../../preview/components/SidebarStack.js";
|
|
7
|
+
import { studioStore } from "../studioStore.js";
|
|
7
8
|
import { FollowAgentContext } from "../followAgentContext.js";
|
|
8
9
|
import { useThread } from "../../../lib/use-thread.js";
|
|
9
10
|
import { EditorWhereContext } from "../editorWhereContext.js";
|
|
10
11
|
import { useEditorSelection } from "../useEditorSelection.js";
|
|
12
|
+
import { ConnectAgent } from "./ConnectAgent.js";
|
|
11
13
|
import { ThreadComposer } from "./ThreadComposer.js";
|
|
12
14
|
import { ThreadMessages } from "./ThreadMessages.js";
|
|
13
15
|
import { c } from "react/compiler-runtime";
|
|
14
16
|
import * as React from "react";
|
|
15
17
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
18
|
+
import { useSelector } from "@xstate/store-react";
|
|
16
19
|
import { EMPTY_THREAD_VIEW } from "@camox/api-contract/threads";
|
|
17
20
|
|
|
18
21
|
//#region src/features/studio/components/ChatWidget.tsx
|
|
@@ -23,7 +26,8 @@ import { EMPTY_THREAD_VIEW } from "@camox/api-contract/threads";
|
|
|
23
26
|
* When the agent on Live hands the editor's request off to a draft, the studio that asked enters
|
|
24
27
|
* it, if it still shows Live; the card the Live turn ends on opens it too. Each message tells the
|
|
25
28
|
* agent where the editor is: the page they look at, with what they selected on it unless they
|
|
26
|
-
* cleared it from the composer, or the Canvas with no page selected.
|
|
29
|
+
* cleared it from the composer, or the Canvas with no page selected. "Ask the agent" elsewhere in
|
|
30
|
+
* the studio fills its composer. Under the composer, it invites the editor to connect their own AI.
|
|
27
31
|
*/
|
|
28
32
|
function ChatWidget(t0) {
|
|
29
33
|
const $ = c(6);
|
|
@@ -44,9 +48,9 @@ function ChatWidget(t0) {
|
|
|
44
48
|
children: "Agent"
|
|
45
49
|
})
|
|
46
50
|
});
|
|
47
|
-
t3 = /* @__PURE__ */
|
|
51
|
+
t3 = /* @__PURE__ */ jsxs("div", {
|
|
48
52
|
className: cn("bg-background flex min-h-0 flex-1 flex-col", CARD_BODY_EDGES),
|
|
49
|
-
children: /* @__PURE__ */ jsx(CurrentThread, {})
|
|
53
|
+
children: [/* @__PURE__ */ jsx(CurrentThread, {}), /* @__PURE__ */ jsx(ConnectAgent, {})]
|
|
50
54
|
});
|
|
51
55
|
$[2] = t2;
|
|
52
56
|
$[3] = t3;
|
|
@@ -126,7 +130,7 @@ function CurrentThread() {
|
|
|
126
130
|
return t4;
|
|
127
131
|
}
|
|
128
132
|
function Thread(t0) {
|
|
129
|
-
const $ = c(
|
|
133
|
+
const $ = c(40);
|
|
130
134
|
const { apiUrl, projectId, draftId, onEnterDraft, onOpenDraft } = t0;
|
|
131
135
|
let t1;
|
|
132
136
|
if ($[0] !== onEnterDraft) {
|
|
@@ -176,85 +180,107 @@ function Thread(t0) {
|
|
|
176
180
|
const [cleared, setCleared] = React.useState(null);
|
|
177
181
|
if (cleared !== null && selectionKey !== cleared) setCleared(null);
|
|
178
182
|
const carried = selection && selectionKey !== cleared ? selection : void 0;
|
|
183
|
+
const asked = useSelector(studioStore, _temp);
|
|
179
184
|
let t6;
|
|
180
|
-
|
|
181
|
-
|
|
185
|
+
let t7;
|
|
186
|
+
if ($[11] !== asked) {
|
|
187
|
+
t6 = () => {
|
|
188
|
+
if (asked) studioStore.trigger.takeAgentAsk();
|
|
189
|
+
};
|
|
190
|
+
t7 = [asked];
|
|
191
|
+
$[11] = asked;
|
|
192
|
+
$[12] = t6;
|
|
193
|
+
$[13] = t7;
|
|
194
|
+
} else {
|
|
195
|
+
t6 = $[12];
|
|
196
|
+
t7 = $[13];
|
|
197
|
+
}
|
|
198
|
+
React.useEffect(t6, t7);
|
|
199
|
+
let t8;
|
|
200
|
+
if ($[14] !== carried || $[15] !== follow || $[16] !== send || $[17] !== where) {
|
|
201
|
+
t8 = (text) => {
|
|
182
202
|
follow?.resume();
|
|
183
203
|
send(text, where?.on === "page" && carried ? {
|
|
184
204
|
...where,
|
|
185
205
|
selection: carried
|
|
186
206
|
} : where);
|
|
187
207
|
};
|
|
188
|
-
$[
|
|
189
|
-
$[
|
|
190
|
-
$[
|
|
191
|
-
$[
|
|
192
|
-
$[
|
|
193
|
-
} else
|
|
194
|
-
const sendFollowing =
|
|
195
|
-
let
|
|
196
|
-
if ($[
|
|
197
|
-
|
|
208
|
+
$[14] = carried;
|
|
209
|
+
$[15] = follow;
|
|
210
|
+
$[16] = send;
|
|
211
|
+
$[17] = where;
|
|
212
|
+
$[18] = t8;
|
|
213
|
+
} else t8 = $[18];
|
|
214
|
+
const sendFollowing = t8;
|
|
215
|
+
let t9;
|
|
216
|
+
if ($[19] !== onOpenDraft || $[20] !== sending || $[21] !== view) {
|
|
217
|
+
t9 = /* @__PURE__ */ jsx(ThreadMessages, {
|
|
198
218
|
view,
|
|
199
219
|
sending,
|
|
200
220
|
onOpenDraft
|
|
201
221
|
});
|
|
202
|
-
$[
|
|
203
|
-
$[
|
|
204
|
-
$[
|
|
205
|
-
$[
|
|
206
|
-
} else
|
|
207
|
-
let
|
|
208
|
-
if ($[
|
|
209
|
-
|
|
222
|
+
$[19] = onOpenDraft;
|
|
223
|
+
$[20] = sending;
|
|
224
|
+
$[21] = view;
|
|
225
|
+
$[22] = t9;
|
|
226
|
+
} else t9 = $[22];
|
|
227
|
+
let t10;
|
|
228
|
+
if ($[23] !== error) {
|
|
229
|
+
t10 = error && /* @__PURE__ */ jsx("p", {
|
|
210
230
|
role: "alert",
|
|
211
231
|
className: "text-destructive px-2 pt-2 text-sm",
|
|
212
232
|
children: error
|
|
213
233
|
});
|
|
214
|
-
$[
|
|
215
|
-
$[
|
|
216
|
-
} else
|
|
217
|
-
let t9;
|
|
218
|
-
if ($[22] !== carried?.path) {
|
|
219
|
-
t9 = carried?.path.at(-1);
|
|
220
|
-
$[22] = carried?.path;
|
|
221
|
-
$[23] = t9;
|
|
222
|
-
} else t9 = $[23];
|
|
223
|
-
let t10;
|
|
224
|
-
if ($[24] !== selectionKey) {
|
|
225
|
-
t10 = () => selectionKey !== null && setCleared(selectionKey);
|
|
226
|
-
$[24] = selectionKey;
|
|
227
|
-
$[25] = t10;
|
|
228
|
-
} else t10 = $[25];
|
|
234
|
+
$[23] = error;
|
|
235
|
+
$[24] = t10;
|
|
236
|
+
} else t10 = $[24];
|
|
229
237
|
let t11;
|
|
230
|
-
if ($[
|
|
231
|
-
t11 =
|
|
238
|
+
if ($[25] !== carried?.path) {
|
|
239
|
+
t11 = carried?.path.at(-1);
|
|
240
|
+
$[25] = carried?.path;
|
|
241
|
+
$[26] = t11;
|
|
242
|
+
} else t11 = $[26];
|
|
243
|
+
let t12;
|
|
244
|
+
if ($[27] !== selectionKey) {
|
|
245
|
+
t12 = () => selectionKey !== null && setCleared(selectionKey);
|
|
246
|
+
$[27] = selectionKey;
|
|
247
|
+
$[28] = t12;
|
|
248
|
+
} else t12 = $[28];
|
|
249
|
+
const t13 = asked ?? void 0;
|
|
250
|
+
let t14;
|
|
251
|
+
if ($[29] !== sendFollowing || $[30] !== stop || $[31] !== t11 || $[32] !== t12 || $[33] !== t13 || $[34] !== view.running) {
|
|
252
|
+
t14 = /* @__PURE__ */ jsx(ThreadComposer, {
|
|
232
253
|
onSend: sendFollowing,
|
|
233
254
|
onStop: stop,
|
|
234
|
-
selection:
|
|
235
|
-
onClearSelection:
|
|
236
|
-
running: view.running
|
|
255
|
+
selection: t11,
|
|
256
|
+
onClearSelection: t12,
|
|
257
|
+
running: view.running,
|
|
258
|
+
filled: t13
|
|
237
259
|
});
|
|
238
|
-
$[
|
|
239
|
-
$[
|
|
240
|
-
$[28] = t10;
|
|
241
|
-
$[29] = t9;
|
|
242
|
-
$[30] = view.running;
|
|
260
|
+
$[29] = sendFollowing;
|
|
261
|
+
$[30] = stop;
|
|
243
262
|
$[31] = t11;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
263
|
+
$[32] = t12;
|
|
264
|
+
$[33] = t13;
|
|
265
|
+
$[34] = view.running;
|
|
266
|
+
$[35] = t14;
|
|
267
|
+
} else t14 = $[35];
|
|
268
|
+
let t15;
|
|
269
|
+
if ($[36] !== t10 || $[37] !== t14 || $[38] !== t9) {
|
|
270
|
+
t15 = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
271
|
+
t9,
|
|
272
|
+
t10,
|
|
273
|
+
t14
|
|
251
274
|
] });
|
|
252
|
-
$[
|
|
253
|
-
$[
|
|
254
|
-
$[
|
|
255
|
-
$[
|
|
256
|
-
} else
|
|
257
|
-
return
|
|
275
|
+
$[36] = t10;
|
|
276
|
+
$[37] = t14;
|
|
277
|
+
$[38] = t9;
|
|
278
|
+
$[39] = t15;
|
|
279
|
+
} else t15 = $[39];
|
|
280
|
+
return t15;
|
|
281
|
+
}
|
|
282
|
+
function _temp(state) {
|
|
283
|
+
return state.context.agentAsk;
|
|
258
284
|
}
|
|
259
285
|
|
|
260
286
|
//#endregion
|