@elytracms/studio 0.0.57 → 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.
Files changed (53) hide show
  1. package/dist/components/ai/ai-chat-panel.js +44 -6
  2. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  3. package/dist/components/ai/chat-ui.d.ts +1 -1
  4. package/dist/components/ai/chat-ui.js +11 -2
  5. package/dist/components/ai/chat-ui.js.map +1 -1
  6. package/dist/components/ai/proposal-card.d.ts +30 -7
  7. package/dist/components/ai/proposal-card.js +81 -21
  8. package/dist/components/ai/proposal-card.js.map +1 -1
  9. package/dist/components/cms/detail-editor.js +12 -0
  10. package/dist/components/cms/detail-editor.js.map +1 -1
  11. package/dist/components/cms/document-diff-view.d.ts +12 -1
  12. package/dist/components/cms/document-diff-view.js +119 -42
  13. package/dist/components/cms/document-diff-view.js.map +1 -1
  14. package/dist/components/cms/rich-text-preview.d.ts +15 -0
  15. package/dist/components/cms/rich-text-preview.js +81 -0
  16. package/dist/components/cms/rich-text-preview.js.map +1 -0
  17. package/dist/components/cms/version-compare-text.d.ts +26 -0
  18. package/dist/components/cms/version-compare-text.js +60 -0
  19. package/dist/components/cms/version-compare-text.js.map +1 -1
  20. package/dist/lib/ai/apply-content.js +52 -10
  21. package/dist/lib/ai/apply-content.js.map +1 -1
  22. package/dist/lib/ai/chat-context.d.ts +19 -3
  23. package/dist/lib/ai/chat-context.js +33 -4
  24. package/dist/lib/ai/chat-context.js.map +1 -1
  25. package/dist/lib/ai/chat-state.d.ts +6 -3
  26. package/dist/lib/ai/chat-state.js.map +1 -1
  27. package/dist/lib/ai/fake-provider.d.ts +60 -5
  28. package/dist/lib/ai/fake-provider.js +74 -6
  29. package/dist/lib/ai/fake-provider.js.map +1 -1
  30. package/dist/lib/ai/index.d.ts +2 -0
  31. package/dist/lib/ai/index.js +2 -0
  32. package/dist/lib/ai/index.js.map +1 -1
  33. package/dist/lib/ai/rich-text-input.d.ts +27 -0
  34. package/dist/lib/ai/rich-text-input.js +25 -0
  35. package/dist/lib/ai/rich-text-input.js.map +1 -0
  36. package/dist/lib/ai/shape.d.ts +12 -1
  37. package/dist/lib/ai/shape.js +46 -20
  38. package/dist/lib/ai/shape.js.map +1 -1
  39. package/dist/lib/ai/tools.d.ts +88 -13
  40. package/dist/lib/ai/tools.js +253 -14
  41. package/dist/lib/ai/tools.js.map +1 -1
  42. package/dist/lib/ai/types.d.ts +20 -0
  43. package/dist/lib/ai/types.js.map +1 -1
  44. package/dist/lib/ai/update-fields.d.ts +68 -0
  45. package/dist/lib/ai/update-fields.js +296 -0
  46. package/dist/lib/ai/update-fields.js.map +1 -0
  47. package/dist/lib/ai-current-document.d.ts +53 -0
  48. package/dist/lib/ai-current-document.js +54 -0
  49. package/dist/lib/ai-current-document.js.map +1 -0
  50. package/dist/lib/cms-views/view-model.d.ts +6 -6
  51. package/dist/lib/i18n/messages.en.js +10 -1
  52. package/dist/lib/i18n/messages.en.js.map +1 -1
  53. package/package.json +2 -2
@@ -9,7 +9,8 @@ import { translate } from '@elytracms/studio/lib/i18n';
9
9
  import { STUDIO_PROJECT_ROUTE, studioPath, useNavigate } from '@elytracms/studio/router';
10
10
  import { resolveDeploymentAi } from '@elytracms/studio/lib/workspace';
11
11
  import { getStudioOperations } from '@elytracms/studio/lib/operations';
12
- import { DEFAULT_MAX_STEPS, buildChatContext, chatReviewReducer, clearConversation, createChatCandidateStore, createChatToolExecutor, initialChatReviewState, resolveAiConfig, resolveChatTarget, resolveKnowledge, withStepCap, } from '@elytracms/studio/lib/ai';
12
+ import { DEFAULT_MAX_STEPS, buildChatContext, chatReviewReducer, clearConversation, createChatCandidateStore, createChatToolExecutor, initialChatReviewState, renderCurrentDocumentSection, resolveAiConfig, resolveChatTarget, resolveKnowledge, withStepCap, } from '@elytracms/studio/lib/ai';
13
+ import { getAiCurrentDocument } from '@elytracms/studio/lib/ai-current-document';
13
14
  import { useCms } from '../cms/cms-store.js';
14
15
  import { validateComposition, validateRichTextEmbeds } from '../cms/cms-composition-validators.js';
15
16
  import { AssistantTurn, ChatErrorNote, Conversation, ConversationEmpty, PromptInput, ToolStatus, UserTurn, WorkingIndicator, describeToolPart, messageText, segmentMessageParts, } from './chat-ui.js';
@@ -145,15 +146,35 @@ export function AiChatPanel() {
145
146
  headers: toTransportHeaders(chatTarget.headers),
146
147
  // The `body` THUNK — re-evaluated per request (fresh context every turn,
147
148
  // continuations included): collections-catalog summary + client-resolved
148
- // knowledge. The route folds these sections into the system prompt; it
149
- // resolves no data itself (zero-data-access proxy). EC-378's
150
- // current-document block joins via `extraSections`.
149
+ // knowledge + EC-378's current-document block (read from the seam store
150
+ // at REQUEST time, so every turn sees what is open NOW). The route folds
151
+ // these sections into the system prompt; it resolves no data itself
152
+ // (zero-data-access proxy). The block is advisory — the deterministic
153
+ // omitted-target fallback lives in the executor.
151
154
  body: async () => {
152
155
  const knowledge = await resolveKnowledge(knowledgeSources, { readCollection });
153
156
  knowledgeRef.current = knowledge;
154
- return { context: buildChatContext({ collections: cms.collections, knowledge }) };
157
+ const current = getAiCurrentDocument();
158
+ return {
159
+ context: buildChatContext({
160
+ collections: cms.collections,
161
+ knowledge,
162
+ ...(current
163
+ ? {
164
+ extraSections: [
165
+ renderCurrentDocumentSection({
166
+ collection: current.collection,
167
+ id: current.id,
168
+ title: current.title,
169
+ locale: activeLocale,
170
+ }),
171
+ ],
172
+ }
173
+ : {}),
174
+ }),
175
+ };
155
176
  },
156
- }), [chatTarget, knowledgeSources, readCollection, cms.collections]);
177
+ }), [chatTarget, knowledgeSources, readCollection, cms.collections, activeLocale]);
157
178
  // Per-conversation relation-candidate store (EC-380: replaced on clear, so a
158
179
  // fresh conversation cannot reference the previous one's searched ids).
159
180
  const candidatesRef = useRef({
@@ -176,6 +197,9 @@ export function AiChatPanel() {
176
197
  candidates: candidateStore,
177
198
  onProposal: (toolCallId, proposal) => dispatch({ type: 'propose', toolCallId, proposal }),
178
199
  knowledge: () => knowledgeRef.current,
200
+ // EC-378: the current-document seam, read at EXECUTION time — the
201
+ // deterministic fallback when the model omits a tool's target.
202
+ currentDocument: () => getAiCurrentDocument(),
179
203
  locale: activeLocale,
180
204
  ...(cms.localeConfig ? { localeConfig: cms.localeConfig } : {}),
181
205
  validateRichTextEmbeds,
@@ -253,7 +277,12 @@ export function AiChatPanel() {
253
277
  const applyProposal = useCallback(async (proposal) => {
254
278
  if (projectId === undefined)
255
279
  return;
280
+ // For an update/translate of an EXISTING document, spread the stored doc
281
+ // first so document-level extras (alias, defaultLocale) survive the apply —
282
+ // the patch replaces only values/localized.
283
+ const existing = cms.getDocument({ collection: proposal.collection, id: proposal.documentId });
256
284
  const doc = {
285
+ ...(existing ?? {}),
257
286
  collection: proposal.collection,
258
287
  id: proposal.documentId,
259
288
  values: proposal.patch.values,
@@ -281,6 +310,15 @@ export function AiChatPanel() {
281
310
  }
282
311
  // Reflect in the session so the new draft shows in the list + is navigable.
283
312
  cms.saveDocument(doc);
313
+ // EC-378: when the applied proposal targets the document OPEN in the editor,
314
+ // adopt it into the editor's draft buffer — otherwise the editor's autosave
315
+ // would write its stale buffer back and silently revert the applied change.
316
+ const current = getAiCurrentDocument();
317
+ if (current &&
318
+ current.collection === proposal.collection &&
319
+ current.id === proposal.documentId) {
320
+ current.adoptDraft(doc);
321
+ }
284
322
  dispatch({ type: 'apply', toolCallId: proposal.id });
285
323
  }, [projectId, cms]);
286
324
  const openProposal = useCallback((proposal) => {
@@ -1 +1 @@
1
- {"version":3,"file":"ai-chat-panel.js","sourceRoot":"","sources":["../../../src/components/ai/ai-chat-panel.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo, useReducer, useRef, useState } from 'react'\nimport { MessageSquarePlus, X } from 'lucide-react'\nimport { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from 'ai'\nimport type { UIMessage } from 'ai'\nimport { useChat } from '@ai-sdk/react'\nimport {\n createCollectionRegistry,\n resolveDocumentTitle,\n} from '@elytracms/core/cms-core'\nimport type { CmsDocument, FieldValues } from '@elytracms/core/cms-core'\nimport { Button, Heading, Row, Spacer, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { STUDIO_PROJECT_ROUTE, studioPath, useNavigate } from '@elytracms/studio/router'\nimport { resolveDeploymentAi } from '@elytracms/studio/lib/workspace'\nimport { getStudioOperations } from '@elytracms/studio/lib/operations'\nimport {\n DEFAULT_MAX_STEPS,\n buildChatContext,\n chatReviewReducer,\n clearConversation,\n createChatCandidateStore,\n createChatToolExecutor,\n initialChatReviewState,\n resolveAiConfig,\n resolveChatTarget,\n resolveKnowledge,\n withStepCap,\n} from '@elytracms/studio/lib/ai'\nimport type {\n ChatCandidateStore,\n ChatToolReaders,\n KnowledgeCollectionReader,\n ProposalReview,\n ResolvedKnowledge,\n RouteHeaders,\n} from '@elytracms/studio/lib/ai'\nimport { useCms } from '../cms/cms-store'\nimport { validateComposition, validateRichTextEmbeds } from '../cms/cms-composition-validators'\nimport {\n AssistantTurn,\n ChatErrorNote,\n Conversation,\n ConversationEmpty,\n PromptInput,\n ToolStatus,\n UserTurn,\n WorkingIndicator,\n describeToolPart,\n messageText,\n segmentMessageParts,\n} from './chat-ui'\nimport type { MessagePartView } from './chat-ui'\nimport { ProposalCard } from './proposal-card'\nimport { setAiPanelOpen } from '@elytracms/studio/lib/ai-panel'\n\n/**\n * The AI assistant chat panel (EC-199, moved onto the SDK's native protocol by\n * EC-334) — the right-hand collapsible surface in the CMS area. `useChat` +\n * `DefaultChatTransport` own the conversation against the route's native chat\n * branch (visible token streaming); `onToolCall` dispatches into the client\n * executor map (operations reads + terminal engines, all with the user's own\n * session); `sendAutomaticallyWhen` is the SDK predicate wrapped with the\n * per-turn step cap. What the SDK does not own lives in the pure review reducer:\n * full proposals keyed by `toolCallId` (reviewed as a diff, applied as a DRAFT\n * via operations upsert + a provenance-tagged snapshot — the EC-199 trust\n * boundary, unmoved) and blocking errors. EC-380: the \"new conversation\"\n * affordance clears busy-safely (stop → new chat id + review reset; pending\n * proposals are discarded with reject semantics).\n *\n * NEVER a modal (house rule). The whole module (including `ai` + `@ai-sdk/react`)\n * sits behind the lazy boundary (`ai-chat-panel-lazy`) — pinned in bundle tests.\n */\n\n/** Adapt a `RouteHeaders` (static or thunk) to the transport's headers resolvable. */\nfunction toTransportHeaders(headers: RouteHeaders | undefined): () => Promise<Record<string, string>> {\n return async () => {\n const resolved = typeof headers === 'function' ? await headers() : headers\n const record: Record<string, string> = {}\n new Headers(resolved ?? {}).forEach((value, key) => {\n record[key] = value\n })\n return record\n }\n}\n\nexport function AiChatPanel() {\n const cms = useCms()\n const navigate = useNavigate()\n const [review, dispatch] = useReducer(chatReviewReducer, initialChatReviewState)\n const [input, setInput] = useState('')\n // EC-380: the conversation counter — bumping it mints a fresh `useChat` id\n // (a brand-new conversation) and a fresh relation-candidate store.\n const [conversation, setConversation] = useState(1)\n\n const projectId = cms.projectId\n const activeLocale = cms.activeLocale\n\n // Resolve the AI platform config once (presence + capability gate). The panel only\n // mounts when configured, but a misconfigured provider still fails loud here.\n const resolved = useMemo(() => resolveAiConfig(resolveDeploymentAi()), [])\n const provider = resolved.resolved?.provider ?? null\n const knowledgeSources = useMemo(() => resolved.resolved?.knowledge ?? [], [resolved])\n const maxRepairAttempts = resolved.resolved?.limits?.maxRepairAttempts\n const ready = provider !== null && resolved.ok && projectId !== undefined\n\n const registry = useMemo(() => createCollectionRegistry(cms.collections), [cms.collections])\n\n /** Merge a document's non-localized + active-locale values for title/prose reads. */\n const localeValues = useCallback(\n (doc: CmsDocument): FieldValues => ({\n ...doc.values,\n ...(doc.localized?.[activeLocale] ?? {}),\n }),\n [activeLocale],\n )\n\n /** Operations-backed tool readers — client-side, with the user's own session. */\n const readers = useMemo<ChatToolReaders>(() => {\n const matches = (query: string | undefined, ...fields: (string | undefined)[]) =>\n !query || fields.some((f) => f?.toLowerCase().includes(query.toLowerCase()))\n return {\n async searchDocuments({ collection, query }) {\n if (projectId === undefined) return []\n const { operations } = await getStudioOperations()\n const result = await operations.documents.list({ projectId, collection })\n if (!result.ok) return []\n const target = cms.getCollection(collection)\n return result.value\n .map((rec) => ({\n id: rec.document.id,\n title: target\n ? resolveDocumentTitle(target, localeValues(rec.document), rec.document.id)\n : rec.document.id,\n }))\n .filter((r) => matches(query, r.title, r.id))\n },\n async listAssets({ query }) {\n if (projectId === undefined) return []\n const { operations } = await getStudioOperations()\n const result = await operations.assets.list({ projectId })\n if (!result.ok) return []\n return result.value\n .map((asset) => ({ id: asset.id, title: asset.title ?? asset.filename }))\n .filter((r) => matches(query, r.title, r.id))\n },\n // Full document (draft values + localized) — the getDocument/translate tools.\n async getDocument({ collection, id }) {\n if (projectId === undefined) return null\n const { operations } = await getStudioOperations()\n const result = await operations.documents.get({ projectId, ref: { collection, id } })\n return result.ok ? result.value?.document ?? null : null\n },\n }\n }, [projectId, cms, localeValues])\n\n /** A fresh, collision-free draft id for the target collection. */\n const mintDocumentId = useCallback(\n (collectionId: string): string => {\n const existing = new Set(cms.documentsOf(collectionId).map((d) => d.id))\n let n = 1\n while (existing.has(`new-${collectionId}-${n}`)) n += 1\n return `new-${collectionId}-${n}`\n },\n [cms],\n )\n\n /** Read `fromCollection(...)` knowledge prose through the operations layer. */\n const readCollection = useCallback<KnowledgeCollectionReader>(\n async (source) => {\n if (projectId === undefined) return []\n const { operations } = await getStudioOperations()\n const result = await operations.documents.list({ projectId, collection: source.collectionId })\n if (!result.ok) return []\n const target = cms.getCollection(source.collectionId)\n const field =\n source.field ?? target?.fields.find((f) => f.type === 'text' || f.type === 'richText')?.name\n return result.value\n .map((rec) => {\n const values = localeValues(rec.document)\n const content = field ? String(values[field] ?? '') : ''\n return { content }\n })\n .filter((item) => item.content.trim().length > 0)\n },\n [projectId, cms, localeValues],\n )\n\n // The knowledge snapshot the executors read — refreshed by the body thunk on\n // every request, so the terminal engines see what the model saw this turn.\n const knowledgeRef = useRef<ResolvedKnowledge>({ items: [] })\n\n // The chat transport target: the configured route provider's endpoint +\n // session-header thunk (EC-371), re-evaluated per request.\n const chatTarget = useMemo(() => resolveChatTarget(provider), [provider])\n\n const transport = useMemo(\n () =>\n new DefaultChatTransport({\n api: chatTarget.endpoint,\n headers: toTransportHeaders(chatTarget.headers),\n // The `body` THUNK — re-evaluated per request (fresh context every turn,\n // continuations included): collections-catalog summary + client-resolved\n // knowledge. The route folds these sections into the system prompt; it\n // resolves no data itself (zero-data-access proxy). EC-378's\n // current-document block joins via `extraSections`.\n body: async () => {\n const knowledge = await resolveKnowledge(knowledgeSources, { readCollection })\n knowledgeRef.current = knowledge\n return { context: buildChatContext({ collections: cms.collections, knowledge }) }\n },\n }),\n [chatTarget, knowledgeSources, readCollection, cms.collections],\n )\n\n // Per-conversation relation-candidate store (EC-380: replaced on clear, so a\n // fresh conversation cannot reference the previous one's searched ids).\n const candidatesRef = useRef<{ conversation: number; store: ChatCandidateStore }>({\n conversation,\n store: createChatCandidateStore(),\n })\n if (candidatesRef.current.conversation !== conversation) {\n candidatesRef.current = { conversation, store: createChatCandidateStore() }\n }\n const candidateStore = candidatesRef.current.store\n\n const executor = useMemo(() => {\n if (!provider) return null\n return createChatToolExecutor({\n provider,\n registry,\n collections: cms.collections,\n readers,\n mintDocumentId,\n candidates: candidateStore,\n onProposal: (toolCallId, proposal) => dispatch({ type: 'propose', toolCallId, proposal }),\n knowledge: () => knowledgeRef.current,\n locale: activeLocale,\n ...(cms.localeConfig ? { localeConfig: cms.localeConfig } : {}),\n validateRichTextEmbeds,\n validateComposition,\n ...(maxRepairAttempts !== undefined ? { maxRepairAttempts } : {}),\n })\n }, [\n provider,\n registry,\n cms.collections,\n cms.localeConfig,\n readers,\n mintDocumentId,\n activeLocale,\n maxRepairAttempts,\n candidateStore,\n ])\n\n const chat = useChat({\n // A new conversation is a new chat id — `useChat` recreates its state.\n id: `elytra-assistant-${conversation}`,\n transport,\n onToolCall: async ({ toolCall }) => {\n if (!executor) return\n const outcome = await executor.execute({\n toolCallId: toolCall.toolCallId,\n toolName: toolCall.toolName,\n input: toolCall.input,\n })\n // Deliberately NOT awaited: `onToolCall` runs inside the chat's serial\n // stream job, and `addToolOutput` enqueues on the same executor —\n // awaiting it here deadlocks the stream (verified against ai@7.0.22).\n if (outcome.ok) {\n void chat.addToolOutput({\n tool: toolCall.toolName,\n toolCallId: toolCall.toolCallId,\n output: outcome.output,\n })\n } else {\n void chat.addToolOutput({\n state: 'output-error',\n tool: toolCall.toolName,\n toolCallId: toolCall.toolCallId,\n errorText: outcome.errorText,\n })\n }\n },\n // The SDK predicate wrapped with the per-user-turn step cap: a capped turn\n // stops cleanly with a visible note — never a silent stall (EC-334).\n sendAutomaticallyWhen: withStepCap(lastAssistantMessageIsCompleteWithToolCalls, {\n maxSteps: DEFAULT_MAX_STEPS,\n onCap: () =>\n dispatch({ type: 'fail', message: translate('ai.error.stepCap', { max: DEFAULT_MAX_STEPS }) }),\n }),\n onError: () => dispatch({ type: 'fail', message: translate('ai.error.route') }),\n })\n\n const busy = chat.status === 'submitted' || chat.status === 'streaming'\n\n const submit = useCallback(() => {\n const text = input.trim()\n if (text.length === 0 || busy || !ready) return\n setInput('')\n dispatch({ type: 'clear-error' })\n void chat.sendMessage({ text })\n }, [input, busy, ready, chat])\n\n // EC-380: clear / new topic — busy-safe (stop first), pending proposals\n // discarded with reject semantics (the review state resets; nothing was applied).\n const startNewConversation = useCallback(() => {\n void clearConversation({ status: chat.status, stop: chat.stop }, () => {\n dispatch({ type: 'clear' })\n setInput('')\n setConversation((current) => current + 1)\n })\n }, [chat.status, chat.stop])\n\n const applyProposal = useCallback(\n async (proposal: ProposalReview) => {\n if (projectId === undefined) return\n const doc: CmsDocument = {\n collection: proposal.collection,\n id: proposal.documentId,\n values: proposal.patch.values,\n ...(proposal.patch.localized ? { localized: proposal.patch.localized } : {}),\n }\n const { operations } = await getStudioOperations()\n const upserted = await operations.documents.upsert({ projectId, document: doc })\n if (!upserted.ok) {\n dispatch({ type: 'fail', message: translate('ai.error.applyFailed') })\n return\n }\n // Provenance: record an AI-tagged version snapshot (a restore point + the\n // 'assistant' origin surfaced in the versions pane). Best-effort — the draft\n // already landed above; a snapshot failure must not lose the applied content.\n try {\n await operations.documents.snapshot({\n projectId,\n ref: { collection: proposal.collection, id: proposal.documentId },\n label: translate('cms.version.aiAuthoredTitle'),\n origin: 'assistant',\n })\n } catch {\n // ignore — provenance is additive\n }\n // Reflect in the session so the new draft shows in the list + is navigable.\n cms.saveDocument(doc)\n dispatch({ type: 'apply', toolCallId: proposal.id })\n },\n [projectId, cms],\n )\n\n const openProposal = useCallback(\n (proposal: ProposalReview) => {\n if (projectId === undefined) return\n void navigate({\n to: STUDIO_PROJECT_ROUTE,\n params: { projectId, _splat: studioPath.cmsDocument(proposal.collection, proposal.documentId) },\n })\n },\n [navigate, projectId],\n )\n\n const hasProposal = useCallback(\n (toolCallId: string) => review.proposals[toolCallId] !== undefined,\n [review.proposals],\n )\n\n const renderAssistantMessage = (message: UIMessage) => {\n const segments = segmentMessageParts(message.parts as MessagePartView[], hasProposal)\n return segments.map((segment, index) => {\n if (segment.kind === 'text') {\n return (\n <AssistantTurn key={`${message.id}-${index}`}>\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {segment.text}\n </Text>\n </AssistantTurn>\n )\n }\n if (segment.kind === 'tools') {\n return (\n <AssistantTurn key={`${message.id}-${index}`}>\n <ToolStatus steps={segment.parts.map(describeToolPart)} />\n </AssistantTurn>\n )\n }\n const proposal = review.proposals[segment.toolCallId]\n if (!proposal) return null\n return (\n <ProposalCard\n key={segment.toolCallId}\n proposal={proposal}\n onApply={() => void applyProposal(proposal)}\n onDiscard={() => dispatch({ type: 'reject', toolCallId: proposal.id })}\n onOpen={() => openProposal(proposal)}\n />\n )\n })\n }\n\n return (\n <aside className=\"flex h-full min-h-0 w-[360px] shrink-0 flex-col border-l border-border bg-card\">\n <Row gap=\"2\" align=\"center\" className=\"h-12 shrink-0 border-b border-border px-3\">\n <Heading level={2} size=\"h4\">\n {translate('ai.panel.title')}\n </Heading>\n <Spacer />\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n onClick={startNewConversation}\n disabled={chat.messages.length === 0 && review.error === null && !busy}\n title={translate('ai.panel.newConversation')}\n aria-label={translate('ai.panel.newConversation')}\n >\n <MessageSquarePlus />\n </Button>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n onClick={() => setAiPanelOpen(false)}\n title={translate('ai.panel.close')}\n aria-label={translate('ai.panel.close')}\n >\n <X />\n </Button>\n </Row>\n\n <Conversation atBottomKey={chat.messages}>\n {chat.messages.length === 0 ? <ConversationEmpty /> : null}\n {chat.messages.map((message) =>\n message.role === 'user' ? (\n <UserTurn key={message.id} text={messageText(message)} />\n ) : (\n <div key={message.id} className=\"contents\">\n {renderAssistantMessage(message)}\n </div>\n ),\n )}\n {chat.status === 'submitted' ? (\n <AssistantTurn>\n <WorkingIndicator />\n </AssistantTurn>\n ) : null}\n {review.error !== null ? (\n <AssistantTurn>\n <ChatErrorNote message={review.error} />\n </AssistantTurn>\n ) : null}\n </Conversation>\n\n {ready ? (\n <PromptInput value={input} onChange={setInput} onSubmit={submit} disabled={busy} />\n ) : (\n <div className=\"border-t border-border p-3\">\n <Text size=\"xs\" tone=\"muted\">\n {translate('ai.panel.unavailable')}\n </Text>\n </div>\n )}\n </aside>\n )\n}\n"]}
1
+ {"version":3,"file":"ai-chat-panel.js","sourceRoot":"","sources":["../../../src/components/ai/ai-chat-panel.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo, useReducer, useRef, useState } from 'react'\nimport { MessageSquarePlus, X } from 'lucide-react'\nimport { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from 'ai'\nimport type { UIMessage } from 'ai'\nimport { useChat } from '@ai-sdk/react'\nimport {\n createCollectionRegistry,\n resolveDocumentTitle,\n} from '@elytracms/core/cms-core'\nimport type { CmsDocument, FieldValues } from '@elytracms/core/cms-core'\nimport { Button, Heading, Row, Spacer, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { STUDIO_PROJECT_ROUTE, studioPath, useNavigate } from '@elytracms/studio/router'\nimport { resolveDeploymentAi } from '@elytracms/studio/lib/workspace'\nimport { getStudioOperations } from '@elytracms/studio/lib/operations'\nimport {\n DEFAULT_MAX_STEPS,\n buildChatContext,\n chatReviewReducer,\n clearConversation,\n createChatCandidateStore,\n createChatToolExecutor,\n initialChatReviewState,\n renderCurrentDocumentSection,\n resolveAiConfig,\n resolveChatTarget,\n resolveKnowledge,\n withStepCap,\n} from '@elytracms/studio/lib/ai'\nimport { getAiCurrentDocument } from '@elytracms/studio/lib/ai-current-document'\nimport type {\n ChatCandidateStore,\n ChatToolReaders,\n KnowledgeCollectionReader,\n ProposalReview,\n ResolvedKnowledge,\n RouteHeaders,\n} from '@elytracms/studio/lib/ai'\nimport { useCms } from '../cms/cms-store'\nimport { validateComposition, validateRichTextEmbeds } from '../cms/cms-composition-validators'\nimport {\n AssistantTurn,\n ChatErrorNote,\n Conversation,\n ConversationEmpty,\n PromptInput,\n ToolStatus,\n UserTurn,\n WorkingIndicator,\n describeToolPart,\n messageText,\n segmentMessageParts,\n} from './chat-ui'\nimport type { MessagePartView } from './chat-ui'\nimport { ProposalCard } from './proposal-card'\nimport { setAiPanelOpen } from '@elytracms/studio/lib/ai-panel'\n\n/**\n * The AI assistant chat panel (EC-199, moved onto the SDK's native protocol by\n * EC-334) — the right-hand collapsible surface in the CMS area. `useChat` +\n * `DefaultChatTransport` own the conversation against the route's native chat\n * branch (visible token streaming); `onToolCall` dispatches into the client\n * executor map (operations reads + terminal engines, all with the user's own\n * session); `sendAutomaticallyWhen` is the SDK predicate wrapped with the\n * per-turn step cap. What the SDK does not own lives in the pure review reducer:\n * full proposals keyed by `toolCallId` (reviewed as a diff, applied as a DRAFT\n * via operations upsert + a provenance-tagged snapshot — the EC-199 trust\n * boundary, unmoved) and blocking errors. EC-380: the \"new conversation\"\n * affordance clears busy-safely (stop → new chat id + review reset; pending\n * proposals are discarded with reject semantics).\n *\n * NEVER a modal (house rule). The whole module (including `ai` + `@ai-sdk/react`)\n * sits behind the lazy boundary (`ai-chat-panel-lazy`) — pinned in bundle tests.\n */\n\n/** Adapt a `RouteHeaders` (static or thunk) to the transport's headers resolvable. */\nfunction toTransportHeaders(headers: RouteHeaders | undefined): () => Promise<Record<string, string>> {\n return async () => {\n const resolved = typeof headers === 'function' ? await headers() : headers\n const record: Record<string, string> = {}\n new Headers(resolved ?? {}).forEach((value, key) => {\n record[key] = value\n })\n return record\n }\n}\n\nexport function AiChatPanel() {\n const cms = useCms()\n const navigate = useNavigate()\n const [review, dispatch] = useReducer(chatReviewReducer, initialChatReviewState)\n const [input, setInput] = useState('')\n // EC-380: the conversation counter — bumping it mints a fresh `useChat` id\n // (a brand-new conversation) and a fresh relation-candidate store.\n const [conversation, setConversation] = useState(1)\n\n const projectId = cms.projectId\n const activeLocale = cms.activeLocale\n\n // Resolve the AI platform config once (presence + capability gate). The panel only\n // mounts when configured, but a misconfigured provider still fails loud here.\n const resolved = useMemo(() => resolveAiConfig(resolveDeploymentAi()), [])\n const provider = resolved.resolved?.provider ?? null\n const knowledgeSources = useMemo(() => resolved.resolved?.knowledge ?? [], [resolved])\n const maxRepairAttempts = resolved.resolved?.limits?.maxRepairAttempts\n const ready = provider !== null && resolved.ok && projectId !== undefined\n\n const registry = useMemo(() => createCollectionRegistry(cms.collections), [cms.collections])\n\n /** Merge a document's non-localized + active-locale values for title/prose reads. */\n const localeValues = useCallback(\n (doc: CmsDocument): FieldValues => ({\n ...doc.values,\n ...(doc.localized?.[activeLocale] ?? {}),\n }),\n [activeLocale],\n )\n\n /** Operations-backed tool readers — client-side, with the user's own session. */\n const readers = useMemo<ChatToolReaders>(() => {\n const matches = (query: string | undefined, ...fields: (string | undefined)[]) =>\n !query || fields.some((f) => f?.toLowerCase().includes(query.toLowerCase()))\n return {\n async searchDocuments({ collection, query }) {\n if (projectId === undefined) return []\n const { operations } = await getStudioOperations()\n const result = await operations.documents.list({ projectId, collection })\n if (!result.ok) return []\n const target = cms.getCollection(collection)\n return result.value\n .map((rec) => ({\n id: rec.document.id,\n title: target\n ? resolveDocumentTitle(target, localeValues(rec.document), rec.document.id)\n : rec.document.id,\n }))\n .filter((r) => matches(query, r.title, r.id))\n },\n async listAssets({ query }) {\n if (projectId === undefined) return []\n const { operations } = await getStudioOperations()\n const result = await operations.assets.list({ projectId })\n if (!result.ok) return []\n return result.value\n .map((asset) => ({ id: asset.id, title: asset.title ?? asset.filename }))\n .filter((r) => matches(query, r.title, r.id))\n },\n // Full document (draft values + localized) — the getDocument/translate tools.\n async getDocument({ collection, id }) {\n if (projectId === undefined) return null\n const { operations } = await getStudioOperations()\n const result = await operations.documents.get({ projectId, ref: { collection, id } })\n return result.ok ? result.value?.document ?? null : null\n },\n }\n }, [projectId, cms, localeValues])\n\n /** A fresh, collision-free draft id for the target collection. */\n const mintDocumentId = useCallback(\n (collectionId: string): string => {\n const existing = new Set(cms.documentsOf(collectionId).map((d) => d.id))\n let n = 1\n while (existing.has(`new-${collectionId}-${n}`)) n += 1\n return `new-${collectionId}-${n}`\n },\n [cms],\n )\n\n /** Read `fromCollection(...)` knowledge prose through the operations layer. */\n const readCollection = useCallback<KnowledgeCollectionReader>(\n async (source) => {\n if (projectId === undefined) return []\n const { operations } = await getStudioOperations()\n const result = await operations.documents.list({ projectId, collection: source.collectionId })\n if (!result.ok) return []\n const target = cms.getCollection(source.collectionId)\n const field =\n source.field ?? target?.fields.find((f) => f.type === 'text' || f.type === 'richText')?.name\n return result.value\n .map((rec) => {\n const values = localeValues(rec.document)\n const content = field ? String(values[field] ?? '') : ''\n return { content }\n })\n .filter((item) => item.content.trim().length > 0)\n },\n [projectId, cms, localeValues],\n )\n\n // The knowledge snapshot the executors read — refreshed by the body thunk on\n // every request, so the terminal engines see what the model saw this turn.\n const knowledgeRef = useRef<ResolvedKnowledge>({ items: [] })\n\n // The chat transport target: the configured route provider's endpoint +\n // session-header thunk (EC-371), re-evaluated per request.\n const chatTarget = useMemo(() => resolveChatTarget(provider), [provider])\n\n const transport = useMemo(\n () =>\n new DefaultChatTransport({\n api: chatTarget.endpoint,\n headers: toTransportHeaders(chatTarget.headers),\n // The `body` THUNK — re-evaluated per request (fresh context every turn,\n // continuations included): collections-catalog summary + client-resolved\n // knowledge + EC-378's current-document block (read from the seam store\n // at REQUEST time, so every turn sees what is open NOW). The route folds\n // these sections into the system prompt; it resolves no data itself\n // (zero-data-access proxy). The block is advisory — the deterministic\n // omitted-target fallback lives in the executor.\n body: async () => {\n const knowledge = await resolveKnowledge(knowledgeSources, { readCollection })\n knowledgeRef.current = knowledge\n const current = getAiCurrentDocument()\n return {\n context: buildChatContext({\n collections: cms.collections,\n knowledge,\n ...(current\n ? {\n extraSections: [\n renderCurrentDocumentSection({\n collection: current.collection,\n id: current.id,\n title: current.title,\n locale: activeLocale,\n }),\n ],\n }\n : {}),\n }),\n }\n },\n }),\n [chatTarget, knowledgeSources, readCollection, cms.collections, activeLocale],\n )\n\n // Per-conversation relation-candidate store (EC-380: replaced on clear, so a\n // fresh conversation cannot reference the previous one's searched ids).\n const candidatesRef = useRef<{ conversation: number; store: ChatCandidateStore }>({\n conversation,\n store: createChatCandidateStore(),\n })\n if (candidatesRef.current.conversation !== conversation) {\n candidatesRef.current = { conversation, store: createChatCandidateStore() }\n }\n const candidateStore = candidatesRef.current.store\n\n const executor = useMemo(() => {\n if (!provider) return null\n return createChatToolExecutor({\n provider,\n registry,\n collections: cms.collections,\n readers,\n mintDocumentId,\n candidates: candidateStore,\n onProposal: (toolCallId, proposal) => dispatch({ type: 'propose', toolCallId, proposal }),\n knowledge: () => knowledgeRef.current,\n // EC-378: the current-document seam, read at EXECUTION time — the\n // deterministic fallback when the model omits a tool's target.\n currentDocument: () => getAiCurrentDocument(),\n locale: activeLocale,\n ...(cms.localeConfig ? { localeConfig: cms.localeConfig } : {}),\n validateRichTextEmbeds,\n validateComposition,\n ...(maxRepairAttempts !== undefined ? { maxRepairAttempts } : {}),\n })\n }, [\n provider,\n registry,\n cms.collections,\n cms.localeConfig,\n readers,\n mintDocumentId,\n activeLocale,\n maxRepairAttempts,\n candidateStore,\n ])\n\n const chat = useChat({\n // A new conversation is a new chat id — `useChat` recreates its state.\n id: `elytra-assistant-${conversation}`,\n transport,\n onToolCall: async ({ toolCall }) => {\n if (!executor) return\n const outcome = await executor.execute({\n toolCallId: toolCall.toolCallId,\n toolName: toolCall.toolName,\n input: toolCall.input,\n })\n // Deliberately NOT awaited: `onToolCall` runs inside the chat's serial\n // stream job, and `addToolOutput` enqueues on the same executor —\n // awaiting it here deadlocks the stream (verified against ai@7.0.22).\n if (outcome.ok) {\n void chat.addToolOutput({\n tool: toolCall.toolName,\n toolCallId: toolCall.toolCallId,\n output: outcome.output,\n })\n } else {\n void chat.addToolOutput({\n state: 'output-error',\n tool: toolCall.toolName,\n toolCallId: toolCall.toolCallId,\n errorText: outcome.errorText,\n })\n }\n },\n // The SDK predicate wrapped with the per-user-turn step cap: a capped turn\n // stops cleanly with a visible note — never a silent stall (EC-334).\n sendAutomaticallyWhen: withStepCap(lastAssistantMessageIsCompleteWithToolCalls, {\n maxSteps: DEFAULT_MAX_STEPS,\n onCap: () =>\n dispatch({ type: 'fail', message: translate('ai.error.stepCap', { max: DEFAULT_MAX_STEPS }) }),\n }),\n onError: () => dispatch({ type: 'fail', message: translate('ai.error.route') }),\n })\n\n const busy = chat.status === 'submitted' || chat.status === 'streaming'\n\n const submit = useCallback(() => {\n const text = input.trim()\n if (text.length === 0 || busy || !ready) return\n setInput('')\n dispatch({ type: 'clear-error' })\n void chat.sendMessage({ text })\n }, [input, busy, ready, chat])\n\n // EC-380: clear / new topic — busy-safe (stop first), pending proposals\n // discarded with reject semantics (the review state resets; nothing was applied).\n const startNewConversation = useCallback(() => {\n void clearConversation({ status: chat.status, stop: chat.stop }, () => {\n dispatch({ type: 'clear' })\n setInput('')\n setConversation((current) => current + 1)\n })\n }, [chat.status, chat.stop])\n\n const applyProposal = useCallback(\n async (proposal: ProposalReview) => {\n if (projectId === undefined) return\n // For an update/translate of an EXISTING document, spread the stored doc\n // first so document-level extras (alias, defaultLocale) survive the apply —\n // the patch replaces only values/localized.\n const existing = cms.getDocument({ collection: proposal.collection, id: proposal.documentId })\n const doc: CmsDocument = {\n ...(existing ?? {}),\n collection: proposal.collection,\n id: proposal.documentId,\n values: proposal.patch.values,\n ...(proposal.patch.localized ? { localized: proposal.patch.localized } : {}),\n }\n const { operations } = await getStudioOperations()\n const upserted = await operations.documents.upsert({ projectId, document: doc })\n if (!upserted.ok) {\n dispatch({ type: 'fail', message: translate('ai.error.applyFailed') })\n return\n }\n // Provenance: record an AI-tagged version snapshot (a restore point + the\n // 'assistant' origin surfaced in the versions pane). Best-effort — the draft\n // already landed above; a snapshot failure must not lose the applied content.\n try {\n await operations.documents.snapshot({\n projectId,\n ref: { collection: proposal.collection, id: proposal.documentId },\n label: translate('cms.version.aiAuthoredTitle'),\n origin: 'assistant',\n })\n } catch {\n // ignore — provenance is additive\n }\n // Reflect in the session so the new draft shows in the list + is navigable.\n cms.saveDocument(doc)\n // EC-378: when the applied proposal targets the document OPEN in the editor,\n // adopt it into the editor's draft buffer — otherwise the editor's autosave\n // would write its stale buffer back and silently revert the applied change.\n const current = getAiCurrentDocument()\n if (\n current &&\n current.collection === proposal.collection &&\n current.id === proposal.documentId\n ) {\n current.adoptDraft(doc)\n }\n dispatch({ type: 'apply', toolCallId: proposal.id })\n },\n [projectId, cms],\n )\n\n const openProposal = useCallback(\n (proposal: ProposalReview) => {\n if (projectId === undefined) return\n void navigate({\n to: STUDIO_PROJECT_ROUTE,\n params: { projectId, _splat: studioPath.cmsDocument(proposal.collection, proposal.documentId) },\n })\n },\n [navigate, projectId],\n )\n\n const hasProposal = useCallback(\n (toolCallId: string) => review.proposals[toolCallId] !== undefined,\n [review.proposals],\n )\n\n const renderAssistantMessage = (message: UIMessage) => {\n const segments = segmentMessageParts(message.parts as MessagePartView[], hasProposal)\n return segments.map((segment, index) => {\n if (segment.kind === 'text') {\n return (\n <AssistantTurn key={`${message.id}-${index}`}>\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {segment.text}\n </Text>\n </AssistantTurn>\n )\n }\n if (segment.kind === 'tools') {\n return (\n <AssistantTurn key={`${message.id}-${index}`}>\n <ToolStatus steps={segment.parts.map(describeToolPart)} />\n </AssistantTurn>\n )\n }\n const proposal = review.proposals[segment.toolCallId]\n if (!proposal) return null\n return (\n <ProposalCard\n key={segment.toolCallId}\n proposal={proposal}\n onApply={() => void applyProposal(proposal)}\n onDiscard={() => dispatch({ type: 'reject', toolCallId: proposal.id })}\n onOpen={() => openProposal(proposal)}\n />\n )\n })\n }\n\n return (\n <aside className=\"flex h-full min-h-0 w-[360px] shrink-0 flex-col border-l border-border bg-card\">\n <Row gap=\"2\" align=\"center\" className=\"h-12 shrink-0 border-b border-border px-3\">\n <Heading level={2} size=\"h4\">\n {translate('ai.panel.title')}\n </Heading>\n <Spacer />\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n onClick={startNewConversation}\n disabled={chat.messages.length === 0 && review.error === null && !busy}\n title={translate('ai.panel.newConversation')}\n aria-label={translate('ai.panel.newConversation')}\n >\n <MessageSquarePlus />\n </Button>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n onClick={() => setAiPanelOpen(false)}\n title={translate('ai.panel.close')}\n aria-label={translate('ai.panel.close')}\n >\n <X />\n </Button>\n </Row>\n\n <Conversation atBottomKey={chat.messages}>\n {chat.messages.length === 0 ? <ConversationEmpty /> : null}\n {chat.messages.map((message) =>\n message.role === 'user' ? (\n <UserTurn key={message.id} text={messageText(message)} />\n ) : (\n <div key={message.id} className=\"contents\">\n {renderAssistantMessage(message)}\n </div>\n ),\n )}\n {chat.status === 'submitted' ? (\n <AssistantTurn>\n <WorkingIndicator />\n </AssistantTurn>\n ) : null}\n {review.error !== null ? (\n <AssistantTurn>\n <ChatErrorNote message={review.error} />\n </AssistantTurn>\n ) : null}\n </Conversation>\n\n {ready ? (\n <PromptInput value={input} onChange={setInput} onSubmit={submit} disabled={busy} />\n ) : (\n <div className=\"border-t border-border p-3\">\n <Text size=\"xs\" tone=\"muted\">\n {translate('ai.panel.unavailable')}\n </Text>\n </div>\n )}\n </aside>\n )\n}\n"]}
@@ -42,7 +42,7 @@ export type MessageSegment = {
42
42
  export declare function segmentMessageParts(parts: readonly MessagePartView[], hasProposal: (toolCallId: string) => boolean): MessageSegment[];
43
43
  /** One presentational tool-status line. */
44
44
  export interface ChatToolStep {
45
- icon: 'search' | 'assets' | 'document' | 'draft' | 'translate';
45
+ icon: 'search' | 'assets' | 'document' | 'draft' | 'translate' | 'edit';
46
46
  label: string;
47
47
  /** Still running (no output yet) — renders the pulse affordance. */
48
48
  running: boolean;
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useRef } from 'react';
3
- import { FileText, Images, Languages, Search, Sparkles, TriangleAlert } from 'lucide-react';
3
+ import { FileText, Images, Languages, PencilLine, Search, Sparkles, TriangleAlert } from 'lucide-react';
4
4
  import { Button, Row, Stack, Text } from '@elytracms/studio/ui';
5
5
  import { translate } from '@elytracms/studio/lib/i18n';
6
6
  import { cn } from '@elytracms/studio/lib/utils';
7
7
  /** The terminal tools whose `output-available` part is a reviewable proposal. */
8
- const TERMINAL_TOOLS = new Set(['createDocument', 'translateDocument']);
8
+ const TERMINAL_TOOLS = new Set(['createDocument', 'translateDocument', 'updateFields']);
9
9
  /** The tool name behind a tool part's `type` (`tool-<name>` / `dynamic-tool`). */
10
10
  export function toolPartName(part) {
11
11
  if (part.type === 'dynamic-tool') {
@@ -105,6 +105,13 @@ export function describeToolPart(part) {
105
105
  }
106
106
  return { icon: 'translate', label: translate('ai.step.translatingDoc'), running };
107
107
  }
108
+ case 'updateFields': {
109
+ const output = part.output;
110
+ if (!running && output?.status === 'no-change') {
111
+ return { icon: 'edit', label: translate('ai.step.updateNoChange'), running: false };
112
+ }
113
+ return { icon: 'edit', label: translate('ai.step.updatingDoc'), running };
114
+ }
108
115
  default:
109
116
  return { icon: 'draft', label: tool, running };
110
117
  }
@@ -161,6 +168,8 @@ function StepIcon({ icon }) {
161
168
  return _jsx(Images, { className: className });
162
169
  if (icon === 'translate')
163
170
  return _jsx(Languages, { className: className });
171
+ if (icon === 'edit')
172
+ return _jsx(PencilLine, { className: className });
164
173
  if (icon === 'document')
165
174
  return _jsx(FileText, { className: className });
166
175
  return _jsx(FileText, { className: className });
@@ -1 +1 @@
1
- {"version":3,"file":"chat-ui.js","sourceRoot":"","sources":["../../../src/components/ai/chat-ui.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useRef } from 'react'\nimport type { KeyboardEvent, ReactNode } from 'react'\nimport { FileText, Images, Languages, Search, Sparkles, TriangleAlert } from 'lucide-react'\n// Type-only (erased at compile; `verbatimModuleSyntax`): the UIMessage part shapes.\nimport type { UIMessage } from 'ai'\nimport { Button, Row, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { cn } from '@elytracms/studio/lib/utils'\n\n/**\n * AI-Elements-style chat primitives (EC-199, reworked for EC-334), vendored as\n * local shadcn-style components (named, Tailwind-token) rather than a package\n * dependency. The conversation now renders the AI SDK's UIMessages directly:\n * streaming text parts grow token-by-token, tool parts render as the ToolStatus\n * affordance (`input-streaming → input-available → output-available`), and the\n * terminal tools' parts render as `ProposalCard` (the panel wires that — this\n * module stays purely presentational; pure part-mapping cores are exported for\n * direct node tests, house pattern).\n */\n\n// --- UIMessage part mapping (pure cores) --------------------------------------------\n\n/** One UIMessage part, viewed structurally (the fields the chat surface reads). */\nexport interface MessagePartView {\n type: string\n text?: string\n toolCallId?: string\n state?: string\n input?: unknown\n output?: unknown\n errorText?: string\n}\n\n/** The terminal tools whose `output-available` part is a reviewable proposal. */\nconst TERMINAL_TOOLS = new Set(['createDocument', 'translateDocument'])\n\n/** The tool name behind a tool part's `type` (`tool-<name>` / `dynamic-tool`). */\nexport function toolPartName(part: MessagePartView): string | null {\n if (part.type === 'dynamic-tool') {\n return typeof (part as { toolName?: unknown }).toolName === 'string'\n ? ((part as { toolName?: string }).toolName ?? null)\n : null\n }\n return part.type.startsWith('tool-') ? part.type.slice('tool-'.length) : null\n}\n\n/** A rendered segment of one assistant message. */\nexport type MessageSegment =\n | { kind: 'text'; text: string }\n | { kind: 'tools'; parts: MessagePartView[] }\n | { kind: 'proposal'; toolCallId: string }\n\n/**\n * Fold an assistant UIMessage's parts into render segments: consecutive tool\n * parts group into ONE ToolStatus block (the EC-199 affordance), a terminal\n * tool part with a registered proposal becomes a `proposal` segment, and text\n * parts pass through (empty ones dropped). Pure — tested directly.\n */\nexport function segmentMessageParts(\n parts: readonly MessagePartView[],\n hasProposal: (toolCallId: string) => boolean,\n): MessageSegment[] {\n const segments: MessageSegment[] = []\n for (const part of parts) {\n if (part.type === 'text') {\n const text = part.text ?? ''\n if (text.length > 0) segments.push({ kind: 'text', text })\n continue\n }\n const tool = toolPartName(part)\n if (tool === null) continue // step-start / reasoning / data-* — not rendered\n if (\n TERMINAL_TOOLS.has(tool) &&\n part.toolCallId !== undefined &&\n part.state === 'output-available' &&\n hasProposal(part.toolCallId)\n ) {\n segments.push({ kind: 'proposal', toolCallId: part.toolCallId })\n continue\n }\n const last = segments.at(-1)\n if (last?.kind === 'tools') last.parts.push(part)\n else segments.push({ kind: 'tools', parts: [part] })\n }\n return segments\n}\n\n/** One presentational tool-status line. */\nexport interface ChatToolStep {\n icon: 'search' | 'assets' | 'document' | 'draft' | 'translate'\n label: string\n /** Still running (no output yet) — renders the pulse affordance. */\n running: boolean\n}\n\nfunction inputField(part: MessagePartView, field: string): string | undefined {\n const input = part.input\n if (typeof input !== 'object' || input === null) return undefined\n const value = (input as Record<string, unknown>)[field]\n return typeof value === 'string' ? value : undefined\n}\n\n/**\n * Map one tool part onto its status line (`input-streaming → input-available →\n * output-available | output-error`). Pure — tested directly.\n */\nexport function describeToolPart(part: MessagePartView): ChatToolStep {\n const tool = toolPartName(part) ?? 'tool'\n const running = part.state === 'input-streaming' || part.state === 'input-available'\n const failed = part.state === 'output-error'\n if (failed) {\n return {\n icon: 'draft',\n label: part.errorText ?? translate('ai.error.generic'),\n running: false,\n }\n }\n switch (tool) {\n case 'searchDocuments': {\n const collection = inputField(part, 'collection') ?? '…'\n if (running) return { icon: 'search', label: translate('ai.step.searchRunning', { collection }), running }\n const count = Array.isArray(part.output) ? part.output.length : 0\n return { icon: 'search', label: translate('ai.step.search', { collection, count }), running }\n }\n case 'listAssets': {\n if (running) return { icon: 'assets', label: translate('ai.step.listAssetsRunning'), running }\n const count = Array.isArray(part.output) ? part.output.length : 0\n return { icon: 'assets', label: translate('ai.step.listAssets', { count }), running }\n }\n case 'getDocument':\n return {\n icon: 'document',\n label: running\n ? translate('ai.step.getDocumentRunning')\n : translate('ai.step.getDocument'),\n running,\n }\n case 'createDocument':\n return {\n icon: 'draft',\n label: translate('ai.step.drafting', { collection: inputField(part, 'collection') ?? '…' }),\n running,\n }\n case 'translateDocument': {\n const output = part.output as { status?: unknown } | undefined\n if (!running && output?.status === 'nothing-to-translate') {\n return { icon: 'translate', label: translate('ai.step.translateNothing'), running: false }\n }\n return { icon: 'translate', label: translate('ai.step.translatingDoc'), running }\n }\n default:\n return { icon: 'draft', label: tool, running }\n }\n}\n\n/** Join a UIMessage's text parts (the user-turn renderer). */\nexport function messageText(message: Pick<UIMessage, 'parts'>): string {\n return (message.parts as MessagePartView[])\n .filter((part) => part.type === 'text')\n .map((part) => part.text ?? '')\n .join('\\n')\n}\n\n// --- Presentational components -------------------------------------------------------\n\n/** The scrolling conversation column; auto-scrolls to the newest turn. */\nexport function Conversation({ children, atBottomKey }: { children: ReactNode; atBottomKey: unknown }) {\n const ref = useRef<HTMLDivElement>(null)\n useEffect(() => {\n const el = ref.current\n if (el) el.scrollTop = el.scrollHeight\n }, [atBottomKey])\n return (\n <div ref={ref} className=\"min-h-0 flex-1 overflow-auto\">\n <Stack gap=\"3\" className=\"p-3\">\n {children}\n </Stack>\n </div>\n )\n}\n\n/** A user turn — a right-aligned bubble. */\nexport function UserTurn({ text }: { text: string }) {\n return (\n <Row justify=\"end\">\n <div className=\"max-w-[85%] rounded-2xl rounded-br-sm bg-primary px-3 py-2 text-primary-foreground\">\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words text-primary-foreground\">\n {text}\n </Text>\n </div>\n </Row>\n )\n}\n\n/** An assistant text turn — a left-aligned calm block. */\nexport function AssistantTurn({ children }: { children: ReactNode }) {\n return (\n <Row justify=\"start\" align=\"start\" gap=\"2\">\n <div className=\"mt-0.5 grid size-6 shrink-0 place-items-center rounded-full bg-muted text-muted-foreground\">\n <Sparkles className=\"size-3.5\" />\n </div>\n <div className=\"min-w-0 flex-1\">{children}</div>\n </Row>\n )\n}\n\n/** The empty-state prompt shown before the first message. */\nexport function ConversationEmpty() {\n return (\n <Stack gap=\"2\" align=\"center\" className=\"px-4 py-10 text-center\">\n <div className=\"grid size-10 place-items-center rounded-full bg-muted text-muted-foreground\">\n <Sparkles className=\"size-5\" />\n </div>\n <Text size=\"sm\" weight=\"medium\">\n {translate('ai.panel.emptyTitle')}\n </Text>\n <Text size=\"xs\" tone=\"muted\" className=\"max-w-[24rem]\">\n {translate('ai.panel.emptyHint')}\n </Text>\n </Stack>\n )\n}\n\n/** The tool-status affordance — the tool steps of one segment, as calm status lines. */\nexport function ToolStatus({ steps }: { steps: ChatToolStep[] }) {\n if (steps.length === 0) return null\n return (\n <Stack gap=\"1\" className=\"rounded-lg border border-border/60 bg-muted/30 px-2.5 py-2\">\n {steps.map((step, i) => (\n <Row key={i} gap=\"2\" align=\"center\">\n <StepIcon icon={step.icon} />\n <Text size=\"xs\" tone=\"muted\">\n {step.label}\n </Text>\n {step.running ? (\n <span\n className=\"inline-block size-1.5 animate-pulse rounded-full bg-muted-foreground\"\n aria-hidden\n />\n ) : null}\n </Row>\n ))}\n </Stack>\n )\n}\n\n/** The busy indicator shown while the route request is in flight (pre-stream). */\nexport function WorkingIndicator() {\n return (\n <Row gap=\"2\" align=\"center\">\n <span className=\"inline-block size-1.5 animate-pulse rounded-full bg-muted-foreground\" aria-hidden />\n <Text size=\"xs\" tone=\"muted\">\n {translate('ai.panel.working')}\n </Text>\n </Row>\n )\n}\n\n/** A blocking-error note (route/provider failure, step cap) — visible, never silent. */\nexport function ChatErrorNote({ message }: { message: string }) {\n return (\n <Row gap=\"2\" align=\"start\" className=\"rounded-lg border border-destructive/30 bg-destructive/5 px-2.5 py-2\">\n <TriangleAlert className=\"mt-0.5 size-3.5 shrink-0 text-destructive\" />\n <Text size=\"xs\" className=\"text-destructive\">\n {message}\n </Text>\n </Row>\n )\n}\n\nfunction StepIcon({ icon }: { icon: ChatToolStep['icon'] }) {\n const className = 'size-3.5 shrink-0 text-muted-foreground'\n if (icon === 'search') return <Search className={className} />\n if (icon === 'assets') return <Images className={className} />\n if (icon === 'translate') return <Languages className={className} />\n if (icon === 'document') return <FileText className={className} />\n return <FileText className={className} />\n}\n\n/** The prompt input — a growing textarea + send button; Enter sends, Shift+Enter newline. */\nexport function PromptInput({\n value,\n onChange,\n onSubmit,\n disabled,\n}: {\n value: string\n onChange: (value: string) => void\n onSubmit: () => void\n disabled: boolean\n}) {\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault()\n if (!disabled && value.trim().length > 0) onSubmit()\n }\n },\n [disabled, value, onSubmit],\n )\n return (\n <div className=\"border-t border-border p-2.5\">\n <div\n className={cn(\n 'flex items-end gap-2 rounded-xl border border-border bg-background px-2.5 py-2',\n 'focus-within:border-primary/60',\n )}\n >\n <textarea\n value={value}\n onChange={(event) => onChange(event.target.value)}\n onKeyDown={handleKeyDown}\n rows={1}\n placeholder={translate('ai.panel.placeholder')}\n aria-label={translate('ai.panel.placeholder')}\n className=\"max-h-40 min-h-[1.5rem] flex-1 resize-none bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n />\n <Button\n size=\"sm\"\n onClick={onSubmit}\n disabled={disabled || value.trim().length === 0}\n title={translate('ai.panel.send')}\n aria-label={translate('ai.panel.send')}\n >\n {translate('ai.panel.send')}\n </Button>\n </div>\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"chat-ui.js","sourceRoot":"","sources":["../../../src/components/ai/chat-ui.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useRef } from 'react'\nimport type { KeyboardEvent, ReactNode } from 'react'\nimport { FileText, Images, Languages, PencilLine, Search, Sparkles, TriangleAlert } from 'lucide-react'\n// Type-only (erased at compile; `verbatimModuleSyntax`): the UIMessage part shapes.\nimport type { UIMessage } from 'ai'\nimport { Button, Row, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { cn } from '@elytracms/studio/lib/utils'\n\n/**\n * AI-Elements-style chat primitives (EC-199, reworked for EC-334), vendored as\n * local shadcn-style components (named, Tailwind-token) rather than a package\n * dependency. The conversation now renders the AI SDK's UIMessages directly:\n * streaming text parts grow token-by-token, tool parts render as the ToolStatus\n * affordance (`input-streaming → input-available → output-available`), and the\n * terminal tools' parts render as `ProposalCard` (the panel wires that — this\n * module stays purely presentational; pure part-mapping cores are exported for\n * direct node tests, house pattern).\n */\n\n// --- UIMessage part mapping (pure cores) --------------------------------------------\n\n/** One UIMessage part, viewed structurally (the fields the chat surface reads). */\nexport interface MessagePartView {\n type: string\n text?: string\n toolCallId?: string\n state?: string\n input?: unknown\n output?: unknown\n errorText?: string\n}\n\n/** The terminal tools whose `output-available` part is a reviewable proposal. */\nconst TERMINAL_TOOLS = new Set(['createDocument', 'translateDocument', 'updateFields'])\n\n/** The tool name behind a tool part's `type` (`tool-<name>` / `dynamic-tool`). */\nexport function toolPartName(part: MessagePartView): string | null {\n if (part.type === 'dynamic-tool') {\n return typeof (part as { toolName?: unknown }).toolName === 'string'\n ? ((part as { toolName?: string }).toolName ?? null)\n : null\n }\n return part.type.startsWith('tool-') ? part.type.slice('tool-'.length) : null\n}\n\n/** A rendered segment of one assistant message. */\nexport type MessageSegment =\n | { kind: 'text'; text: string }\n | { kind: 'tools'; parts: MessagePartView[] }\n | { kind: 'proposal'; toolCallId: string }\n\n/**\n * Fold an assistant UIMessage's parts into render segments: consecutive tool\n * parts group into ONE ToolStatus block (the EC-199 affordance), a terminal\n * tool part with a registered proposal becomes a `proposal` segment, and text\n * parts pass through (empty ones dropped). Pure — tested directly.\n */\nexport function segmentMessageParts(\n parts: readonly MessagePartView[],\n hasProposal: (toolCallId: string) => boolean,\n): MessageSegment[] {\n const segments: MessageSegment[] = []\n for (const part of parts) {\n if (part.type === 'text') {\n const text = part.text ?? ''\n if (text.length > 0) segments.push({ kind: 'text', text })\n continue\n }\n const tool = toolPartName(part)\n if (tool === null) continue // step-start / reasoning / data-* — not rendered\n if (\n TERMINAL_TOOLS.has(tool) &&\n part.toolCallId !== undefined &&\n part.state === 'output-available' &&\n hasProposal(part.toolCallId)\n ) {\n segments.push({ kind: 'proposal', toolCallId: part.toolCallId })\n continue\n }\n const last = segments.at(-1)\n if (last?.kind === 'tools') last.parts.push(part)\n else segments.push({ kind: 'tools', parts: [part] })\n }\n return segments\n}\n\n/** One presentational tool-status line. */\nexport interface ChatToolStep {\n icon: 'search' | 'assets' | 'document' | 'draft' | 'translate' | 'edit'\n label: string\n /** Still running (no output yet) — renders the pulse affordance. */\n running: boolean\n}\n\nfunction inputField(part: MessagePartView, field: string): string | undefined {\n const input = part.input\n if (typeof input !== 'object' || input === null) return undefined\n const value = (input as Record<string, unknown>)[field]\n return typeof value === 'string' ? value : undefined\n}\n\n/**\n * Map one tool part onto its status line (`input-streaming → input-available →\n * output-available | output-error`). Pure — tested directly.\n */\nexport function describeToolPart(part: MessagePartView): ChatToolStep {\n const tool = toolPartName(part) ?? 'tool'\n const running = part.state === 'input-streaming' || part.state === 'input-available'\n const failed = part.state === 'output-error'\n if (failed) {\n return {\n icon: 'draft',\n label: part.errorText ?? translate('ai.error.generic'),\n running: false,\n }\n }\n switch (tool) {\n case 'searchDocuments': {\n const collection = inputField(part, 'collection') ?? '…'\n if (running) return { icon: 'search', label: translate('ai.step.searchRunning', { collection }), running }\n const count = Array.isArray(part.output) ? part.output.length : 0\n return { icon: 'search', label: translate('ai.step.search', { collection, count }), running }\n }\n case 'listAssets': {\n if (running) return { icon: 'assets', label: translate('ai.step.listAssetsRunning'), running }\n const count = Array.isArray(part.output) ? part.output.length : 0\n return { icon: 'assets', label: translate('ai.step.listAssets', { count }), running }\n }\n case 'getDocument':\n return {\n icon: 'document',\n label: running\n ? translate('ai.step.getDocumentRunning')\n : translate('ai.step.getDocument'),\n running,\n }\n case 'createDocument':\n return {\n icon: 'draft',\n label: translate('ai.step.drafting', { collection: inputField(part, 'collection') ?? '…' }),\n running,\n }\n case 'translateDocument': {\n const output = part.output as { status?: unknown } | undefined\n if (!running && output?.status === 'nothing-to-translate') {\n return { icon: 'translate', label: translate('ai.step.translateNothing'), running: false }\n }\n return { icon: 'translate', label: translate('ai.step.translatingDoc'), running }\n }\n case 'updateFields': {\n const output = part.output as { status?: unknown } | undefined\n if (!running && output?.status === 'no-change') {\n return { icon: 'edit', label: translate('ai.step.updateNoChange'), running: false }\n }\n return { icon: 'edit', label: translate('ai.step.updatingDoc'), running }\n }\n default:\n return { icon: 'draft', label: tool, running }\n }\n}\n\n/** Join a UIMessage's text parts (the user-turn renderer). */\nexport function messageText(message: Pick<UIMessage, 'parts'>): string {\n return (message.parts as MessagePartView[])\n .filter((part) => part.type === 'text')\n .map((part) => part.text ?? '')\n .join('\\n')\n}\n\n// --- Presentational components -------------------------------------------------------\n\n/** The scrolling conversation column; auto-scrolls to the newest turn. */\nexport function Conversation({ children, atBottomKey }: { children: ReactNode; atBottomKey: unknown }) {\n const ref = useRef<HTMLDivElement>(null)\n useEffect(() => {\n const el = ref.current\n if (el) el.scrollTop = el.scrollHeight\n }, [atBottomKey])\n return (\n <div ref={ref} className=\"min-h-0 flex-1 overflow-auto\">\n <Stack gap=\"3\" className=\"p-3\">\n {children}\n </Stack>\n </div>\n )\n}\n\n/** A user turn — a right-aligned bubble. */\nexport function UserTurn({ text }: { text: string }) {\n return (\n <Row justify=\"end\">\n <div className=\"max-w-[85%] rounded-2xl rounded-br-sm bg-primary px-3 py-2 text-primary-foreground\">\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words text-primary-foreground\">\n {text}\n </Text>\n </div>\n </Row>\n )\n}\n\n/** An assistant text turn — a left-aligned calm block. */\nexport function AssistantTurn({ children }: { children: ReactNode }) {\n return (\n <Row justify=\"start\" align=\"start\" gap=\"2\">\n <div className=\"mt-0.5 grid size-6 shrink-0 place-items-center rounded-full bg-muted text-muted-foreground\">\n <Sparkles className=\"size-3.5\" />\n </div>\n <div className=\"min-w-0 flex-1\">{children}</div>\n </Row>\n )\n}\n\n/** The empty-state prompt shown before the first message. */\nexport function ConversationEmpty() {\n return (\n <Stack gap=\"2\" align=\"center\" className=\"px-4 py-10 text-center\">\n <div className=\"grid size-10 place-items-center rounded-full bg-muted text-muted-foreground\">\n <Sparkles className=\"size-5\" />\n </div>\n <Text size=\"sm\" weight=\"medium\">\n {translate('ai.panel.emptyTitle')}\n </Text>\n <Text size=\"xs\" tone=\"muted\" className=\"max-w-[24rem]\">\n {translate('ai.panel.emptyHint')}\n </Text>\n </Stack>\n )\n}\n\n/** The tool-status affordance — the tool steps of one segment, as calm status lines. */\nexport function ToolStatus({ steps }: { steps: ChatToolStep[] }) {\n if (steps.length === 0) return null\n return (\n <Stack gap=\"1\" className=\"rounded-lg border border-border/60 bg-muted/30 px-2.5 py-2\">\n {steps.map((step, i) => (\n <Row key={i} gap=\"2\" align=\"center\">\n <StepIcon icon={step.icon} />\n <Text size=\"xs\" tone=\"muted\">\n {step.label}\n </Text>\n {step.running ? (\n <span\n className=\"inline-block size-1.5 animate-pulse rounded-full bg-muted-foreground\"\n aria-hidden\n />\n ) : null}\n </Row>\n ))}\n </Stack>\n )\n}\n\n/** The busy indicator shown while the route request is in flight (pre-stream). */\nexport function WorkingIndicator() {\n return (\n <Row gap=\"2\" align=\"center\">\n <span className=\"inline-block size-1.5 animate-pulse rounded-full bg-muted-foreground\" aria-hidden />\n <Text size=\"xs\" tone=\"muted\">\n {translate('ai.panel.working')}\n </Text>\n </Row>\n )\n}\n\n/** A blocking-error note (route/provider failure, step cap) — visible, never silent. */\nexport function ChatErrorNote({ message }: { message: string }) {\n return (\n <Row gap=\"2\" align=\"start\" className=\"rounded-lg border border-destructive/30 bg-destructive/5 px-2.5 py-2\">\n <TriangleAlert className=\"mt-0.5 size-3.5 shrink-0 text-destructive\" />\n <Text size=\"xs\" className=\"text-destructive\">\n {message}\n </Text>\n </Row>\n )\n}\n\nfunction StepIcon({ icon }: { icon: ChatToolStep['icon'] }) {\n const className = 'size-3.5 shrink-0 text-muted-foreground'\n if (icon === 'search') return <Search className={className} />\n if (icon === 'assets') return <Images className={className} />\n if (icon === 'translate') return <Languages className={className} />\n if (icon === 'edit') return <PencilLine className={className} />\n if (icon === 'document') return <FileText className={className} />\n return <FileText className={className} />\n}\n\n/** The prompt input — a growing textarea + send button; Enter sends, Shift+Enter newline. */\nexport function PromptInput({\n value,\n onChange,\n onSubmit,\n disabled,\n}: {\n value: string\n onChange: (value: string) => void\n onSubmit: () => void\n disabled: boolean\n}) {\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault()\n if (!disabled && value.trim().length > 0) onSubmit()\n }\n },\n [disabled, value, onSubmit],\n )\n return (\n <div className=\"border-t border-border p-2.5\">\n <div\n className={cn(\n 'flex items-end gap-2 rounded-xl border border-border bg-background px-2.5 py-2',\n 'focus-within:border-primary/60',\n )}\n >\n <textarea\n value={value}\n onChange={(event) => onChange(event.target.value)}\n onKeyDown={handleKeyDown}\n rows={1}\n placeholder={translate('ai.panel.placeholder')}\n aria-label={translate('ai.panel.placeholder')}\n className=\"max-h-40 min-h-[1.5rem] flex-1 resize-none bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n />\n <Button\n size=\"sm\"\n onClick={onSubmit}\n disabled={disabled || value.trim().length === 0}\n title={translate('ai.panel.send')}\n aria-label={translate('ai.panel.send')}\n >\n {translate('ai.panel.send')}\n </Button>\n </div>\n </div>\n )\n}\n"]}
@@ -1,15 +1,38 @@
1
- import type { ProposalReview } from '@elytracms/studio/lib/ai';
1
+ import type { CollectionDef } from '@elytracms/core/cms-core';
2
+ import type { ProposalOrigin, ProposalReview } from '@elytracms/studio/lib/ai';
3
+ import type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source';
2
4
  /**
3
- * The AI proposal review (EC-199): a valid, leash-compliant draft the editor reviews
4
- * as a DIFF (the shared EC-317 {@link DocumentDiffView} — never a second renderer)
5
- * before applying it as a draft or discarding it. The diff is `empty-before` vs
6
- * `proposed-after`, wrapped in a `CollectionViewProvider` for the target collection so
7
- * relations/assets render as titles/thumbnails. Apply/discard are handled by the panel
8
- * (the operations write + provenance snapshot happen there); this card is the surface.
5
+ * The AI proposal review (EC-199, extended by EC-332/EC-378): a valid,
6
+ * leash-compliant proposal the editor reviews as a DIFF (the shared EC-317
7
+ * {@link DocumentDiffView} — never a second renderer) before applying it as a
8
+ * draft or discarding it. Create proposals diff `empty-before` vs
9
+ * `proposed-after`; translate/update proposals diff the EXISTING document
10
+ * (old → new). Apply/discard are handled by the panel (the operations write +
11
+ * provenance snapshot happen there); this card is the surface.
12
+ *
13
+ * {@link ProposalCard} is the CMS-wired shell; {@link ProposalCardView} is the
14
+ * presentational card over an injected collection + view source (node-smoked
15
+ * with `renderToStaticMarkup`, house pattern).
9
16
  */
17
+ /** The card's origin-dependent captions (title + diff column headers). */
18
+ export declare function proposalCaptions(origin: ProposalOrigin, collectionLabel: string): {
19
+ title: string;
20
+ unified: string;
21
+ before: string;
22
+ after: string;
23
+ };
10
24
  export declare function ProposalCard({ proposal, onApply, onDiscard, onOpen, }: {
11
25
  proposal: ProposalReview;
12
26
  onApply: () => void;
13
27
  onDiscard: () => void;
14
28
  onOpen: () => void;
15
29
  }): import("react").JSX.Element;
30
+ /** The presentational proposal card over an injected collection + view source. */
31
+ export declare function ProposalCardView({ proposal, collection, source, onApply, onDiscard, onOpen, }: {
32
+ proposal: ProposalReview;
33
+ collection: CollectionDef | undefined;
34
+ source: CollectionViewSource | null;
35
+ onApply: () => void;
36
+ onDiscard: () => void;
37
+ onOpen: () => void;
38
+ }): import("react").JSX.Element;
@@ -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 { ArrowUpRight, Check, Sparkles, X } from 'lucide-react';
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, leash-compliant draft the editor reviews
15
- * as a DIFF (the shared EC-317 {@link DocumentDiffView} — never a second renderer)
16
- * before applying it as a draft or discarding it. The diff is `empty-before` vs
17
- * `proposed-after`, wrapped in a `CollectionViewProvider` for the target collection so
18
- * relations/assets render as titles/thumbnails. Apply/discard are handled by the panel
19
- * (the operations write + provenance snapshot happen there); this card is the surface.
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
- 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: isTranslation
38
- ? translate('ai.translate.proposalTitle', { collection: collectionLabel })
39
- : translate('ai.proposal.title', { collection: collectionLabel }) }), _jsx(Spacer, {}), applied ? _jsx(Badge, { variant: "success", children: translate('ai.proposal.applied') }) : null, rejected ? _jsx(Badge, { variant: "outline", children: translate('ai.proposal.discarded') }) : null] }), _jsx("div", { className: "px-3", children: diff && source ? (_jsx(CollectionViewProvider, { source: source, children: _jsx(DocumentDiffView, { diff: diff, collection: collection, columnHeaders: {
40
- unified: translate('ai.proposal.diffUnified'),
41
- before: isTranslation
42
- ? translate('ai.translate.diffBefore')
43
- : translate('ai.proposal.diffBefore'),
44
- after: isTranslation
45
- ? translate('ai.translate.diffAfter')
46
- : translate('ai.proposal.diffAfter'),
47
- } }) })) : (_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') })) })] }));
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