@elytracms/studio 0.0.56 → 0.0.58
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/convex/auth.ts +7 -0
- package/convex/users.ts +41 -14
- package/dist/components/ai/ai-chat-panel-lazy.js +3 -3
- package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -1
- package/dist/components/ai/ai-chat-panel.d.ts +0 -9
- package/dist/components/ai/ai-chat-panel.js +199 -60
- package/dist/components/ai/ai-chat-panel.js.map +1 -1
- package/dist/components/ai/chat-ui.d.ts +62 -10
- package/dist/components/ai/chat-ui.js +139 -23
- package/dist/components/ai/chat-ui.js.map +1 -1
- package/dist/components/ai/proposal-card.d.ts +30 -7
- package/dist/components/ai/proposal-card.js +81 -21
- package/dist/components/ai/proposal-card.js.map +1 -1
- package/dist/components/cms/detail-editor.js +12 -0
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/document-diff-view.d.ts +12 -1
- package/dist/components/cms/document-diff-view.js +119 -42
- package/dist/components/cms/document-diff-view.js.map +1 -1
- package/dist/components/cms/rich-text-preview.d.ts +15 -0
- package/dist/components/cms/rich-text-preview.js +81 -0
- package/dist/components/cms/rich-text-preview.js.map +1 -0
- package/dist/components/cms/version-compare-text.d.ts +26 -0
- package/dist/components/cms/version-compare-text.js +60 -0
- package/dist/components/cms/version-compare-text.js.map +1 -1
- package/dist/lib/ai/anthropic-provider.d.ts +0 -12
- package/dist/lib/ai/anthropic-provider.js +3 -11
- package/dist/lib/ai/anthropic-provider.js.map +1 -1
- package/dist/lib/ai/apply-content.js +52 -10
- package/dist/lib/ai/apply-content.js.map +1 -1
- package/dist/lib/ai/chat-context.d.ts +74 -0
- package/dist/lib/ai/chat-context.js +100 -0
- package/dist/lib/ai/chat-context.js.map +1 -0
- package/dist/lib/ai/chat-loop.d.ts +70 -0
- package/dist/lib/ai/chat-loop.js +46 -0
- package/dist/lib/ai/chat-loop.js.map +1 -0
- package/dist/lib/ai/chat-state.d.ts +73 -57
- package/dist/lib/ai/chat-state.js +49 -90
- package/dist/lib/ai/chat-state.js.map +1 -1
- package/dist/lib/ai/fake-provider.d.ts +139 -6
- package/dist/lib/ai/fake-provider.js +261 -8
- package/dist/lib/ai/fake-provider.js.map +1 -1
- package/dist/lib/ai/gateway-provider.d.ts +30 -0
- package/dist/lib/ai/gateway-provider.js +73 -0
- package/dist/lib/ai/gateway-provider.js.map +1 -0
- package/dist/lib/ai/index.d.ts +5 -1
- package/dist/lib/ai/index.js +5 -1
- package/dist/lib/ai/index.js.map +1 -1
- package/dist/lib/ai/node-handler.d.ts +13 -4
- package/dist/lib/ai/node-handler.js +33 -3
- package/dist/lib/ai/node-handler.js.map +1 -1
- package/dist/lib/ai/rich-text-input.d.ts +27 -0
- package/dist/lib/ai/rich-text-input.js +25 -0
- package/dist/lib/ai/rich-text-input.js.map +1 -0
- package/dist/lib/ai/route-provider.d.ts +35 -10
- package/dist/lib/ai/route-provider.js +13 -6
- package/dist/lib/ai/route-provider.js.map +1 -1
- package/dist/lib/ai/route.d.ts +50 -21
- package/dist/lib/ai/route.js +69 -40
- package/dist/lib/ai/route.js.map +1 -1
- package/dist/lib/ai/shape.d.ts +12 -1
- package/dist/lib/ai/shape.js +46 -20
- package/dist/lib/ai/shape.js.map +1 -1
- package/dist/lib/ai/tools.d.ts +242 -37
- package/dist/lib/ai/tools.js +548 -63
- package/dist/lib/ai/tools.js.map +1 -1
- package/dist/lib/ai/types.d.ts +31 -16
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/ai/update-fields.d.ts +68 -0
- package/dist/lib/ai/update-fields.js +296 -0
- package/dist/lib/ai/update-fields.js.map +1 -0
- package/dist/lib/ai-current-document.d.ts +53 -0
- package/dist/lib/ai-current-document.js +54 -0
- package/dist/lib/ai-current-document.js.map +1 -0
- package/dist/lib/auth/convex-auth-adapter.d.ts +9 -1
- package/dist/lib/auth/convex-auth-adapter.js +28 -2
- package/dist/lib/auth/convex-auth-adapter.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +6 -6
- package/dist/lib/i18n/messages.en.js +19 -1
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/ui/badge.d.ts +1 -1
- package/package.json +4 -3
- package/dist/lib/ai/create-flow.d.ts +0 -132
- package/dist/lib/ai/create-flow.js +0 -280
- package/dist/lib/ai/create-flow.js.map +0 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo } from 'react';
|
|
3
|
-
import {
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { ArrowUpRight, Check, Maximize2, Sparkles, X } from 'lucide-react';
|
|
4
5
|
import { diffDocuments } from '@elytracms/core/content';
|
|
5
6
|
import { resolveLabel } from '@elytracms/core/cms-core';
|
|
6
|
-
import { Badge, Button, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
|
|
7
|
+
import { Badge, Button, Heading, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
|
|
7
8
|
import { translate } from '@elytracms/studio/lib/i18n';
|
|
8
9
|
import { proposalDocuments } from '@elytracms/studio/lib/ai';
|
|
9
10
|
import { useCms } from '../cms/cms-store.js';
|
|
@@ -11,39 +12,98 @@ import { useCmsCollectionViewSource } from '../collection-view/cms-collection-vi
|
|
|
11
12
|
import { CollectionViewProvider } from '../collection-view/collection-view-context.js';
|
|
12
13
|
import { DocumentDiffView } from '../cms/document-diff-view.js';
|
|
13
14
|
/**
|
|
14
|
-
* The AI proposal review (EC-199): a valid,
|
|
15
|
-
* as a DIFF (the shared EC-317
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* (
|
|
15
|
+
* The AI proposal review (EC-199, extended by EC-332/EC-378): a valid,
|
|
16
|
+
* leash-compliant proposal the editor reviews as a DIFF (the shared EC-317
|
|
17
|
+
* {@link DocumentDiffView} — never a second renderer) before applying it as a
|
|
18
|
+
* draft or discarding it. Create proposals diff `empty-before` vs
|
|
19
|
+
* `proposed-after`; translate/update proposals diff the EXISTING document
|
|
20
|
+
* (old → new). Apply/discard are handled by the panel (the operations write +
|
|
21
|
+
* provenance snapshot happen there); this card is the surface.
|
|
22
|
+
*
|
|
23
|
+
* {@link ProposalCard} is the CMS-wired shell; {@link ProposalCardView} is the
|
|
24
|
+
* presentational card over an injected collection + view source (node-smoked
|
|
25
|
+
* with `renderToStaticMarkup`, house pattern).
|
|
20
26
|
*/
|
|
27
|
+
/** The card's origin-dependent captions (title + diff column headers). */
|
|
28
|
+
export function proposalCaptions(origin, collectionLabel) {
|
|
29
|
+
if (origin === 'translate') {
|
|
30
|
+
return {
|
|
31
|
+
title: translate('ai.translate.proposalTitle', { collection: collectionLabel }),
|
|
32
|
+
unified: translate('ai.proposal.diffUnified'),
|
|
33
|
+
before: translate('ai.translate.diffBefore'),
|
|
34
|
+
after: translate('ai.translate.diffAfter'),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (origin === 'update') {
|
|
38
|
+
return {
|
|
39
|
+
title: translate('ai.update.proposalTitle', { collection: collectionLabel }),
|
|
40
|
+
unified: translate('ai.proposal.diffUnified'),
|
|
41
|
+
before: translate('ai.update.diffBefore'),
|
|
42
|
+
after: translate('ai.update.diffAfter'),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
title: translate('ai.proposal.title', { collection: collectionLabel }),
|
|
47
|
+
unified: translate('ai.proposal.diffUnified'),
|
|
48
|
+
before: translate('ai.proposal.diffBefore'),
|
|
49
|
+
after: translate('ai.proposal.diffAfter'),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
21
52
|
export function ProposalCard({ proposal, onApply, onDiscard, onOpen, }) {
|
|
22
53
|
const cms = useCms();
|
|
23
54
|
const collection = cms.getCollection(proposal.collection);
|
|
24
55
|
const source = useCmsCollectionViewSource(proposal.collection);
|
|
56
|
+
return (_jsx(ProposalCardView, { proposal: proposal, collection: collection, source: source, onApply: onApply, onDiscard: onDiscard, onOpen: onOpen }));
|
|
57
|
+
}
|
|
58
|
+
/** The presentational proposal card over an injected collection + view source. */
|
|
59
|
+
export function ProposalCardView({ proposal, collection, source, onApply, onDiscard, onOpen, }) {
|
|
25
60
|
const diff = useMemo(() => {
|
|
26
61
|
if (!collection)
|
|
27
62
|
return null;
|
|
28
63
|
const { before, after } = proposalDocuments(proposal.patch, proposal.before);
|
|
29
64
|
return diffDocuments(collection, before, after);
|
|
30
65
|
}, [collection, proposal.patch, proposal.before]);
|
|
31
|
-
const isTranslation = proposal.origin === 'translate';
|
|
32
66
|
const collectionLabel = (collection?.form?.labelSingular && resolveLabel(collection.form.labelSingular)) ??
|
|
33
67
|
(collection?.form?.label && resolveLabel(collection.form.label)) ??
|
|
34
68
|
proposal.collection;
|
|
69
|
+
const captions = proposalCaptions(proposal.origin, collectionLabel);
|
|
35
70
|
const applied = proposal.status === 'applied';
|
|
36
71
|
const rejected = proposal.status === 'rejected';
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
72
|
+
const columnHeaders = { unified: captions.unified, before: captions.before, after: captions.after };
|
|
73
|
+
// EC-382 — the expandable review surface: the sidebar card stays stacked; a
|
|
74
|
+
// roomy full-height sheet is offered for a large proposal (never a modal form).
|
|
75
|
+
const [reviewOpen, setReviewOpen] = useState(false);
|
|
76
|
+
const reviewable = diff !== null && source !== null;
|
|
77
|
+
return (_jsxs(Stack, { gap: "2", className: "overflow-hidden rounded-xl border border-border bg-card", children: [_jsxs(Row, { gap: "2", align: "center", className: "border-b border-border/60 bg-muted/30 px-3 py-2", children: [_jsx(Sparkles, { className: "size-4 shrink-0 text-primary" }), _jsx(Text, { size: "sm", weight: "medium", className: "min-w-0 truncate", children: captions.title }), _jsx(Spacer, {}), applied ? _jsx(Badge, { variant: "success", children: translate('ai.proposal.applied') }) : null, rejected ? _jsx(Badge, { variant: "outline", children: translate('ai.proposal.discarded') }) : null, reviewable ? (_jsx(Button, { size: "icon-sm", variant: "ghost", onClick: () => setReviewOpen(true), title: translate('ai.proposal.expandReview'), "aria-label": translate('ai.proposal.expandReview'), children: _jsx(Maximize2, {}) })) : null] }), _jsx("div", { className: "px-3", children: reviewable ? (_jsx(CollectionViewProvider, { source: source, children: _jsx(DocumentDiffView, { diff: diff, collection: collection, columnHeaders: columnHeaders, layout: "stacked" }) })) : (_jsx(Text, { size: "xs", tone: "muted", className: "py-2", children: translate('ai.proposal.unavailable') })) }), _jsx(Row, { gap: "2", align: "center", className: "border-t border-border/60 px-3 py-2", children: proposal.status === 'pending' ? (_jsxs(_Fragment, { children: [_jsx(Text, { size: "xs", tone: "muted", className: "min-w-0 flex-1", children: translate('ai.proposal.reviewHint') }), _jsxs(Button, { size: "sm", variant: "ghost", onClick: onDiscard, children: [_jsx(X, {}), " ", translate('ai.proposal.discard')] }), _jsxs(Button, { size: "sm", onClick: onApply, children: [_jsx(Check, {}), " ", translate('ai.proposal.apply')] })] })) : applied ? (_jsxs(_Fragment, { children: [_jsx(Spacer, {}), _jsxs(Button, { size: "sm", variant: "outline", onClick: onOpen, children: [_jsx(ArrowUpRight, {}), " ", translate('ai.proposal.open')] })] })) : (_jsx(Text, { size: "xs", tone: "muted", children: translate('ai.proposal.discardedNote') })) }), reviewable && reviewOpen ? (_jsx(ProposalReviewSheet, { title: captions.title, diff: diff, collection: collection, source: source, columnHeaders: columnHeaders, status: proposal.status, onApply: onApply, onDiscard: onDiscard, onClose: () => setReviewOpen(false) })) : null] }));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The expandable review surface (EC-382): the proposal's diff promoted into a
|
|
81
|
+
* roomy, full-height right-anchored sheet where the wide `grid` layout is
|
|
82
|
+
* comfortable — for a large proposal the 320px sidebar can only cramp. It is a
|
|
83
|
+
* REVIEW surface, not a form, so it stays lightly dismissible (backdrop click /
|
|
84
|
+
* Escape / close button) rather than a trapping modal — the forms-never-modal
|
|
85
|
+
* spirit, applied. Presentation only: Apply/Discard call the same handlers the
|
|
86
|
+
* card does; it changes no proposal data. Rendered through a portal so it
|
|
87
|
+
* escapes the narrow AI panel; the card default keeps it closed, so the SSR
|
|
88
|
+
* smoke path never mounts the portal.
|
|
89
|
+
*/
|
|
90
|
+
function ProposalReviewSheet({ title, diff, collection, source, columnHeaders, status, onApply, onDiscard, onClose, }) {
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
const onKey = (e) => {
|
|
93
|
+
if (e.key === 'Escape')
|
|
94
|
+
onClose();
|
|
95
|
+
};
|
|
96
|
+
window.addEventListener('keydown', onKey);
|
|
97
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
98
|
+
}, [onClose]);
|
|
99
|
+
if (typeof document === 'undefined')
|
|
100
|
+
return null;
|
|
101
|
+
return createPortal(_jsxs("div", { className: "fixed inset-0 z-50 flex", children: [_jsx("div", { className: "flex-1 bg-background/70 backdrop-blur-sm", "aria-hidden": true, onClick: onClose }), _jsxs("aside", { role: "dialog", "aria-label": title, className: "flex h-full w-[min(720px,92vw)] flex-col border-l border-border bg-card shadow-2xl", children: [_jsxs(Row, { gap: "2", align: "center", className: "h-12 shrink-0 border-b border-border px-4", children: [_jsx(Sparkles, { className: "size-4 shrink-0 text-primary" }), _jsx(Heading, { level: 2, size: "h4", className: "min-w-0 truncate", children: title }), _jsx(Spacer, {}), _jsx(Button, { size: "icon-sm", variant: "ghost", onClick: onClose, title: translate('ai.proposal.collapseReview'), "aria-label": translate('ai.proposal.collapseReview'), children: _jsx(X, {}) })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-4 sm:p-6", children: _jsx("div", { className: "mx-auto w-full max-w-3xl", children: _jsx(CollectionViewProvider, { source: source, children: _jsx(DocumentDiffView, { diff: diff, collection: collection, columnHeaders: columnHeaders }) }) }) }), status === 'pending' ? (_jsxs(Row, { gap: "2", align: "center", className: "shrink-0 border-t border-border px-4 py-3", children: [_jsx(Text, { size: "xs", tone: "muted", className: "min-w-0 flex-1", children: translate('ai.proposal.reviewHint') }), _jsxs(Button, { size: "sm", variant: "ghost", onClick: () => {
|
|
102
|
+
onDiscard();
|
|
103
|
+
onClose();
|
|
104
|
+
}, children: [_jsx(X, {}), " ", translate('ai.proposal.discard')] }), _jsxs(Button, { size: "sm", onClick: () => {
|
|
105
|
+
onApply();
|
|
106
|
+
onClose();
|
|
107
|
+
}, children: [_jsx(Check, {}), " ", translate('ai.proposal.apply')] })] })) : null] })] }), document.body);
|
|
48
108
|
}
|
|
49
109
|
//# sourceMappingURL=proposal-card.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proposal-card.js","sourceRoot":"","sources":["../../../src/components/ai/proposal-card.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react'\nimport { ArrowUpRight, Check, Sparkles, X } from 'lucide-react'\nimport { diffDocuments } from '@elytracms/core/content'\nimport { resolveLabel } from '@elytracms/core/cms-core'\nimport { Badge, Button, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { proposalDocuments } from '@elytracms/studio/lib/ai'\nimport type { ProposalReview } from '@elytracms/studio/lib/ai'\nimport { useCms } from '../cms/cms-store'\nimport { useCmsCollectionViewSource } from '../collection-view/cms-collection-view-source'\nimport { CollectionViewProvider } from '../collection-view/collection-view-context'\nimport { DocumentDiffView } from '../cms/document-diff-view'\n\n/**\n * The AI proposal review (EC-199): a valid, leash-compliant draft the editor reviews\n * as a DIFF (the shared EC-317 {@link DocumentDiffView} — never a second renderer)\n * before applying it as a draft or discarding it. The diff is `empty-before` vs\n * `proposed-after`, wrapped in a `CollectionViewProvider` for the target collection so\n * relations/assets render as titles/thumbnails. Apply/discard are handled by the panel\n * (the operations write + provenance snapshot happen there); this card is the surface.\n */\nexport function ProposalCard({\n proposal,\n onApply,\n onDiscard,\n onOpen,\n}: {\n proposal: ProposalReview\n onApply: () => void\n onDiscard: () => void\n onOpen: () => void\n}) {\n const cms = useCms()\n const collection = cms.getCollection(proposal.collection)\n const source = useCmsCollectionViewSource(proposal.collection)\n\n const diff = useMemo(() => {\n if (!collection) return null\n const { before, after } = proposalDocuments(proposal.patch, proposal.before)\n return diffDocuments(collection, before, after)\n }, [collection, proposal.patch, proposal.before])\n\n const isTranslation = proposal.origin === 'translate'\n\n const collectionLabel =\n (collection?.form?.labelSingular && resolveLabel(collection.form.labelSingular)) ??\n (collection?.form?.label && resolveLabel(collection.form.label)) ??\n proposal.collection\n\n const applied = proposal.status === 'applied'\n const rejected = proposal.status === 'rejected'\n\n return (\n <Stack\n gap=\"2\"\n className=\"overflow-hidden rounded-xl border border-border bg-card\"\n >\n <Row gap=\"2\" align=\"center\" className=\"border-b border-border/60 bg-muted/30 px-3 py-2\">\n <Sparkles className=\"size-4 shrink-0 text-primary\" />\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 truncate\">\n {isTranslation\n ? translate('ai.translate.proposalTitle', { collection: collectionLabel })\n : translate('ai.proposal.title', { collection: collectionLabel })}\n </Text>\n <Spacer />\n {applied ? <Badge variant=\"success\">{translate('ai.proposal.applied')}</Badge> : null}\n {rejected ? <Badge variant=\"outline\">{translate('ai.proposal.discarded')}</Badge> : null}\n </Row>\n\n <div className=\"px-3\">\n {diff && source ? (\n <CollectionViewProvider source={source}>\n <DocumentDiffView\n diff={diff}\n collection={collection!}\n columnHeaders={{\n unified: translate('ai.proposal.diffUnified'),\n before: isTranslation\n ? translate('ai.translate.diffBefore')\n : translate('ai.proposal.diffBefore'),\n after: isTranslation\n ? translate('ai.translate.diffAfter')\n : translate('ai.proposal.diffAfter'),\n }}\n />\n </CollectionViewProvider>\n ) : (\n <Text size=\"xs\" tone=\"muted\" className=\"py-2\">\n {translate('ai.proposal.unavailable')}\n </Text>\n )}\n </div>\n\n <Row gap=\"2\" align=\"center\" className=\"border-t border-border/60 px-3 py-2\">\n {proposal.status === 'pending' ? (\n <>\n <Text size=\"xs\" tone=\"muted\" className=\"min-w-0 flex-1\">\n {translate('ai.proposal.reviewHint')}\n </Text>\n <Button size=\"sm\" variant=\"ghost\" onClick={onDiscard}>\n <X /> {translate('ai.proposal.discard')}\n </Button>\n <Button size=\"sm\" onClick={onApply}>\n <Check /> {translate('ai.proposal.apply')}\n </Button>\n </>\n ) : applied ? (\n <>\n <Spacer />\n <Button size=\"sm\" variant=\"outline\" onClick={onOpen}>\n <ArrowUpRight /> {translate('ai.proposal.open')}\n </Button>\n </>\n ) : (\n <Text size=\"xs\" tone=\"muted\">\n {translate('ai.proposal.discardedNote')}\n </Text>\n )}\n </Row>\n </Stack>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"proposal-card.js","sourceRoot":"","sources":["../../../src/components/ai/proposal-card.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport { ArrowUpRight, Check, Maximize2, Sparkles, X } from 'lucide-react'\nimport { diffDocuments } from '@elytracms/core/content'\nimport type { DocumentDiff } from '@elytracms/core/content'\nimport { resolveLabel } from '@elytracms/core/cms-core'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\nimport { Badge, Button, Heading, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { proposalDocuments } from '@elytracms/studio/lib/ai'\nimport type { ProposalOrigin, ProposalReview } from '@elytracms/studio/lib/ai'\nimport type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useCms } from '../cms/cms-store'\nimport { useCmsCollectionViewSource } from '../collection-view/cms-collection-view-source'\nimport { CollectionViewProvider } from '../collection-view/collection-view-context'\nimport { DocumentDiffView } from '../cms/document-diff-view'\n\n/**\n * The AI proposal review (EC-199, extended by EC-332/EC-378): a valid,\n * leash-compliant proposal the editor reviews as a DIFF (the shared EC-317\n * {@link DocumentDiffView} — never a second renderer) before applying it as a\n * draft or discarding it. Create proposals diff `empty-before` vs\n * `proposed-after`; translate/update proposals diff the EXISTING document\n * (old → new). Apply/discard are handled by the panel (the operations write +\n * provenance snapshot happen there); this card is the surface.\n *\n * {@link ProposalCard} is the CMS-wired shell; {@link ProposalCardView} is the\n * presentational card over an injected collection + view source (node-smoked\n * with `renderToStaticMarkup`, house pattern).\n */\n\n/** The card's origin-dependent captions (title + diff column headers). */\nexport function proposalCaptions(\n origin: ProposalOrigin,\n collectionLabel: string,\n): { title: string; unified: string; before: string; after: string } {\n if (origin === 'translate') {\n return {\n title: translate('ai.translate.proposalTitle', { collection: collectionLabel }),\n unified: translate('ai.proposal.diffUnified'),\n before: translate('ai.translate.diffBefore'),\n after: translate('ai.translate.diffAfter'),\n }\n }\n if (origin === 'update') {\n return {\n title: translate('ai.update.proposalTitle', { collection: collectionLabel }),\n unified: translate('ai.proposal.diffUnified'),\n before: translate('ai.update.diffBefore'),\n after: translate('ai.update.diffAfter'),\n }\n }\n return {\n title: translate('ai.proposal.title', { collection: collectionLabel }),\n unified: translate('ai.proposal.diffUnified'),\n before: translate('ai.proposal.diffBefore'),\n after: translate('ai.proposal.diffAfter'),\n }\n}\n\nexport function ProposalCard({\n proposal,\n onApply,\n onDiscard,\n onOpen,\n}: {\n proposal: ProposalReview\n onApply: () => void\n onDiscard: () => void\n onOpen: () => void\n}) {\n const cms = useCms()\n const collection = cms.getCollection(proposal.collection)\n const source = useCmsCollectionViewSource(proposal.collection)\n return (\n <ProposalCardView\n proposal={proposal}\n collection={collection}\n source={source}\n onApply={onApply}\n onDiscard={onDiscard}\n onOpen={onOpen}\n />\n )\n}\n\n/** The presentational proposal card over an injected collection + view source. */\nexport function ProposalCardView({\n proposal,\n collection,\n source,\n onApply,\n onDiscard,\n onOpen,\n}: {\n proposal: ProposalReview\n collection: CollectionDef | undefined\n source: CollectionViewSource | null\n onApply: () => void\n onDiscard: () => void\n onOpen: () => void\n}) {\n const diff = useMemo(() => {\n if (!collection) return null\n const { before, after } = proposalDocuments(proposal.patch, proposal.before)\n return diffDocuments(collection, before, after)\n }, [collection, proposal.patch, proposal.before])\n\n const collectionLabel =\n (collection?.form?.labelSingular && resolveLabel(collection.form.labelSingular)) ??\n (collection?.form?.label && resolveLabel(collection.form.label)) ??\n proposal.collection\n\n const captions = proposalCaptions(proposal.origin, collectionLabel)\n const applied = proposal.status === 'applied'\n const rejected = proposal.status === 'rejected'\n const columnHeaders = { unified: captions.unified, before: captions.before, after: captions.after }\n\n // EC-382 — the expandable review surface: the sidebar card stays stacked; a\n // roomy full-height sheet is offered for a large proposal (never a modal form).\n const [reviewOpen, setReviewOpen] = useState(false)\n const reviewable = diff !== null && source !== null\n\n return (\n <Stack\n gap=\"2\"\n className=\"overflow-hidden rounded-xl border border-border bg-card\"\n >\n <Row gap=\"2\" align=\"center\" className=\"border-b border-border/60 bg-muted/30 px-3 py-2\">\n <Sparkles className=\"size-4 shrink-0 text-primary\" />\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 truncate\">\n {captions.title}\n </Text>\n <Spacer />\n {applied ? <Badge variant=\"success\">{translate('ai.proposal.applied')}</Badge> : null}\n {rejected ? <Badge variant=\"outline\">{translate('ai.proposal.discarded')}</Badge> : null}\n {reviewable ? (\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n onClick={() => setReviewOpen(true)}\n title={translate('ai.proposal.expandReview')}\n aria-label={translate('ai.proposal.expandReview')}\n >\n <Maximize2 />\n </Button>\n ) : null}\n </Row>\n\n <div className=\"px-3\">\n {reviewable ? (\n <CollectionViewProvider source={source!}>\n <DocumentDiffView\n diff={diff!}\n collection={collection!}\n columnHeaders={columnHeaders}\n layout=\"stacked\"\n />\n </CollectionViewProvider>\n ) : (\n <Text size=\"xs\" tone=\"muted\" className=\"py-2\">\n {translate('ai.proposal.unavailable')}\n </Text>\n )}\n </div>\n\n <Row gap=\"2\" align=\"center\" className=\"border-t border-border/60 px-3 py-2\">\n {proposal.status === 'pending' ? (\n <>\n <Text size=\"xs\" tone=\"muted\" className=\"min-w-0 flex-1\">\n {translate('ai.proposal.reviewHint')}\n </Text>\n <Button size=\"sm\" variant=\"ghost\" onClick={onDiscard}>\n <X /> {translate('ai.proposal.discard')}\n </Button>\n <Button size=\"sm\" onClick={onApply}>\n <Check /> {translate('ai.proposal.apply')}\n </Button>\n </>\n ) : applied ? (\n <>\n <Spacer />\n <Button size=\"sm\" variant=\"outline\" onClick={onOpen}>\n <ArrowUpRight /> {translate('ai.proposal.open')}\n </Button>\n </>\n ) : (\n <Text size=\"xs\" tone=\"muted\">\n {translate('ai.proposal.discardedNote')}\n </Text>\n )}\n </Row>\n\n {reviewable && reviewOpen ? (\n <ProposalReviewSheet\n title={captions.title}\n diff={diff!}\n collection={collection!}\n source={source!}\n columnHeaders={columnHeaders}\n status={proposal.status}\n onApply={onApply}\n onDiscard={onDiscard}\n onClose={() => setReviewOpen(false)}\n />\n ) : null}\n </Stack>\n )\n}\n\n/**\n * The expandable review surface (EC-382): the proposal's diff promoted into a\n * roomy, full-height right-anchored sheet where the wide `grid` layout is\n * comfortable — for a large proposal the 320px sidebar can only cramp. It is a\n * REVIEW surface, not a form, so it stays lightly dismissible (backdrop click /\n * Escape / close button) rather than a trapping modal — the forms-never-modal\n * spirit, applied. Presentation only: Apply/Discard call the same handlers the\n * card does; it changes no proposal data. Rendered through a portal so it\n * escapes the narrow AI panel; the card default keeps it closed, so the SSR\n * smoke path never mounts the portal.\n */\nfunction ProposalReviewSheet({\n title,\n diff,\n collection,\n source,\n columnHeaders,\n status,\n onApply,\n onDiscard,\n onClose,\n}: {\n title: string\n diff: DocumentDiff\n collection: CollectionDef\n source: CollectionViewSource\n columnHeaders: { unified: string; before: string; after: string }\n status: ProposalReview['status']\n onApply: () => void\n onDiscard: () => void\n onClose: () => void\n}) {\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') onClose()\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [onClose])\n\n if (typeof document === 'undefined') return null\n\n return createPortal(\n <div className=\"fixed inset-0 z-50 flex\">\n <div className=\"flex-1 bg-background/70 backdrop-blur-sm\" aria-hidden onClick={onClose} />\n <aside\n role=\"dialog\"\n aria-label={title}\n className=\"flex h-full w-[min(720px,92vw)] flex-col border-l border-border bg-card shadow-2xl\"\n >\n <Row gap=\"2\" align=\"center\" className=\"h-12 shrink-0 border-b border-border px-4\">\n <Sparkles className=\"size-4 shrink-0 text-primary\" />\n <Heading level={2} size=\"h4\" className=\"min-w-0 truncate\">\n {title}\n </Heading>\n <Spacer />\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n onClick={onClose}\n title={translate('ai.proposal.collapseReview')}\n aria-label={translate('ai.proposal.collapseReview')}\n >\n <X />\n </Button>\n </Row>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto p-4 sm:p-6\">\n <div className=\"mx-auto w-full max-w-3xl\">\n <CollectionViewProvider source={source}>\n <DocumentDiffView diff={diff} collection={collection} columnHeaders={columnHeaders} />\n </CollectionViewProvider>\n </div>\n </div>\n\n {status === 'pending' ? (\n <Row gap=\"2\" align=\"center\" className=\"shrink-0 border-t border-border px-4 py-3\">\n <Text size=\"xs\" tone=\"muted\" className=\"min-w-0 flex-1\">\n {translate('ai.proposal.reviewHint')}\n </Text>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n onDiscard()\n onClose()\n }}\n >\n <X /> {translate('ai.proposal.discard')}\n </Button>\n <Button\n size=\"sm\"\n onClick={() => {\n onApply()\n onClose()\n }}\n >\n <Check /> {translate('ai.proposal.apply')}\n </Button>\n </Row>\n ) : null}\n </aside>\n </div>,\n document.body,\n )\n}\n"]}
|
|
@@ -39,6 +39,7 @@ import { DocumentUrlPath } from './document-url-path.js';
|
|
|
39
39
|
import { EmbedInspectorProvider } from './rich-text/embed-inspector-context.js';
|
|
40
40
|
import { EmbedInspector } from './rich-text/embed-inspector.js';
|
|
41
41
|
import { useSetBreadcrumb } from '../breadcrumb-context.js';
|
|
42
|
+
import { useAiCurrentDocumentTap } from '@elytracms/studio/lib/ai-current-document';
|
|
42
43
|
import { resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace';
|
|
43
44
|
// EC-325 — the controller hooks EditorBody orchestrates (each independently
|
|
44
45
|
// unit-tested at its pure core); the shell threads shared draft/ref/replaceDraft
|
|
@@ -133,6 +134,17 @@ function EditorBody({ projectId, collection, doc, focusPath, }) {
|
|
|
133
134
|
setDraft(next);
|
|
134
135
|
setRestoreNonce((n) => n + 1);
|
|
135
136
|
}
|
|
137
|
+
// EC-378 — the current-document seam: publish WHICH document is open (and the
|
|
138
|
+
// draft-adoption channel) so the AI assistant resolves "this document" without
|
|
139
|
+
// asking, and an applied update replaces this buffer instead of being clobbered
|
|
140
|
+
// by the next autosave. A narrow read-only tap + the existing `replaceDraft`;
|
|
141
|
+
// nothing else of the editor is exposed.
|
|
142
|
+
useAiCurrentDocumentTap({
|
|
143
|
+
collection: collection.id,
|
|
144
|
+
id: doc.id,
|
|
145
|
+
title: primaryTitle(draft, collection, locale, source.localeConfig) || doc.id,
|
|
146
|
+
adoptDraft: replaceDraft,
|
|
147
|
+
});
|
|
136
148
|
// EC-226 / EC-316 / EC-325 — the Draft/Published VIEW perspective AND the pinned
|
|
137
149
|
// published snapshot, both derived from the ONE shared version-history query
|
|
138
150
|
// (AD-17: the single operations-backed cache — no raw `source.versioning.load`
|