@elytracms/studio 0.0.56 → 0.0.57

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 (55) hide show
  1. package/convex/auth.ts +7 -0
  2. package/convex/users.ts +41 -14
  3. package/dist/components/ai/ai-chat-panel-lazy.js +3 -3
  4. package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -1
  5. package/dist/components/ai/ai-chat-panel.d.ts +0 -9
  6. package/dist/components/ai/ai-chat-panel.js +161 -60
  7. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  8. package/dist/components/ai/chat-ui.d.ts +62 -10
  9. package/dist/components/ai/chat-ui.js +130 -23
  10. package/dist/components/ai/chat-ui.js.map +1 -1
  11. package/dist/lib/ai/anthropic-provider.d.ts +0 -12
  12. package/dist/lib/ai/anthropic-provider.js +3 -11
  13. package/dist/lib/ai/anthropic-provider.js.map +1 -1
  14. package/dist/lib/ai/chat-context.d.ts +58 -0
  15. package/dist/lib/ai/chat-context.js +71 -0
  16. package/dist/lib/ai/chat-context.js.map +1 -0
  17. package/dist/lib/ai/chat-loop.d.ts +70 -0
  18. package/dist/lib/ai/chat-loop.js +46 -0
  19. package/dist/lib/ai/chat-loop.js.map +1 -0
  20. package/dist/lib/ai/chat-state.d.ts +70 -57
  21. package/dist/lib/ai/chat-state.js +49 -90
  22. package/dist/lib/ai/chat-state.js.map +1 -1
  23. package/dist/lib/ai/fake-provider.d.ts +84 -6
  24. package/dist/lib/ai/fake-provider.js +193 -8
  25. package/dist/lib/ai/fake-provider.js.map +1 -1
  26. package/dist/lib/ai/gateway-provider.d.ts +30 -0
  27. package/dist/lib/ai/gateway-provider.js +73 -0
  28. package/dist/lib/ai/gateway-provider.js.map +1 -0
  29. package/dist/lib/ai/index.d.ts +3 -1
  30. package/dist/lib/ai/index.js +3 -1
  31. package/dist/lib/ai/index.js.map +1 -1
  32. package/dist/lib/ai/node-handler.d.ts +13 -4
  33. package/dist/lib/ai/node-handler.js +33 -3
  34. package/dist/lib/ai/node-handler.js.map +1 -1
  35. package/dist/lib/ai/route-provider.d.ts +35 -10
  36. package/dist/lib/ai/route-provider.js +13 -6
  37. package/dist/lib/ai/route-provider.js.map +1 -1
  38. package/dist/lib/ai/route.d.ts +50 -21
  39. package/dist/lib/ai/route.js +69 -40
  40. package/dist/lib/ai/route.js.map +1 -1
  41. package/dist/lib/ai/tools.d.ts +169 -39
  42. package/dist/lib/ai/tools.js +308 -62
  43. package/dist/lib/ai/tools.js.map +1 -1
  44. package/dist/lib/ai/types.d.ts +11 -16
  45. package/dist/lib/ai/types.js.map +1 -1
  46. package/dist/lib/auth/convex-auth-adapter.d.ts +9 -1
  47. package/dist/lib/auth/convex-auth-adapter.js +28 -2
  48. package/dist/lib/auth/convex-auth-adapter.js.map +1 -1
  49. package/dist/lib/i18n/messages.en.js +9 -0
  50. package/dist/lib/i18n/messages.en.js.map +1 -1
  51. package/dist/ui/badge.d.ts +1 -1
  52. package/package.json +4 -3
  53. package/dist/lib/ai/create-flow.d.ts +0 -132
  54. package/dist/lib/ai/create-flow.js +0 -280
  55. package/dist/lib/ai/create-flow.js.map +0 -1
package/convex/auth.ts CHANGED
@@ -20,12 +20,19 @@ type ConvexAuthProviders = Parameters<typeof convexAuth>[0]['providers']
20
20
  * `ConvexError.data` across that redaction so the sign-in screen can tell the
21
21
  * truth. Codes ONLY — never the raw library message (it can carry the account
22
22
  * id / env-var names).
23
+ *
24
+ * EC-377 widened the family beyond sign-in: `users:changePassword` codes its
25
+ * provider throws with the same table (a wrong CURRENT password is an
26
+ * `InvalidSecret` → `invalid-credentials`), plus `weak-password` for its own
27
+ * min-length guard (the reset flow validates it, `modifyAccountCredentials`
28
+ * does not — and the client UI doesn't pre-validate).
23
29
  */
24
30
  export type StudioAuthErrorCode =
25
31
  | 'invalid-credentials'
26
32
  | 'too-many-attempts'
27
33
  | 'account-exists'
28
34
  | 'deployment-misconfigured'
35
+ | 'weak-password'
29
36
 
30
37
  /**
31
38
  * Map a `@convex-dev/auth` plain-`Error` message to a stable code — or `null`
package/convex/users.ts CHANGED
@@ -4,7 +4,9 @@ import {
4
4
  retrieveAccount,
5
5
  } from '@convex-dev/auth/server'
6
6
  import { makeFunctionReference } from 'convex/server'
7
- import { v } from 'convex/values'
7
+ import { ConvexError, v } from 'convex/values'
8
+ import { toCodedSignInError } from './auth'
9
+ import type { StudioAuthErrorCode } from './auth'
8
10
  import { action, mutation, query } from './functions'
9
11
  import type { QueryCtx } from './functions'
10
12
  import { requireCapability } from './guard'
@@ -146,6 +148,16 @@ const meRef = makeFunctionReference<'query', Record<string, never>, MeResult | n
146
148
  * provider and are NEVER logged or persisted by this code. Self-scoped: it
147
149
  * derives the caller from the JWT and cross-checks the retrieved account is the
148
150
  * caller's own before modifying.
151
+ *
152
+ * EC-377: the provider-call failure path re-throws through the EC-363
153
+ * `toCodedSignInError` wrap — `@convex-dev/auth` throws its failures as PLAIN
154
+ * `Error`s (`retrieveAccount` → `"InvalidSecret"` on a wrong CURRENT password),
155
+ * and a PROD deployment redacts a plain error's `.message` to "Server Error",
156
+ * so without the wrap the client's honest-generic fallback swallowed the truth.
157
+ * Recognized throws become `ConvexError({ code })` (`.data` survives prod
158
+ * redaction; codes ONLY, never the raw message); everything else — including
159
+ * this action's own `unauthorized: …` guards — passes through UNTOUCHED, so
160
+ * which cases fail is unchanged.
149
161
  */
150
162
  export const changePassword = action({
151
163
  args: { currentPassword: v.string(), newPassword: v.string() },
@@ -153,21 +165,36 @@ export const changePassword = action({
153
165
  const userId = await getAuthUserId(ctx)
154
166
  if (userId === null) throw new Error('unauthorized: this function requires an authenticated user')
155
167
  // Enforce the provider's default password policy (min length) — the reset
156
- // flow validates it, but `modifyAccountCredentials` does not.
157
- if (newPassword.length < 8) throw new Error('Password must be at least 8 characters')
168
+ // flow validates it, but `modifyAccountCredentials` does not. Coded (EC-377):
169
+ // the credential panel has no client-side length check, so this guard is
170
+ // user-reachable and must classify truthfully on prod too.
171
+ if (newPassword.length < 8) {
172
+ throw new ConvexError({ code: 'weak-password' satisfies StudioAuthErrorCode })
173
+ }
158
174
  const me = await ctx.runQuery(meRef, {})
159
175
  if (!me?.email) throw new Error('unauthorized: no account email on file')
160
- // Verify the CURRENT password; throws on mismatch (→ invalid-credentials).
161
- const { user } = await retrieveAccount(ctx, {
162
- provider: 'password',
163
- account: { id: me.email, secret: currentPassword },
164
- })
165
- // Defense in depth: the retrieved account MUST be the caller's own.
166
- if (String(user._id) !== String(userId)) throw new Error('unauthorized: account mismatch')
167
- await modifyAccountCredentials(ctx, {
168
- provider: 'password',
169
- account: { id: me.email, secret: newPassword },
170
- })
176
+ try {
177
+ // Verify the CURRENT password; throws on mismatch (→ invalid-credentials).
178
+ const { user } = await retrieveAccount(ctx, {
179
+ provider: 'password',
180
+ account: { id: me.email, secret: currentPassword },
181
+ })
182
+ // Defense in depth: the retrieved account MUST be the caller's own.
183
+ if (String(user._id) !== String(userId)) throw new Error('unauthorized: account mismatch')
184
+ await modifyAccountCredentials(ctx, {
185
+ provider: 'password',
186
+ account: { id: me.email, secret: newPassword },
187
+ })
188
+ } catch (error) {
189
+ const coded = toCodedSignInError(error)
190
+ if (coded !== error) {
191
+ // Keep the ORIGINAL failure visible in the deployment logs — the client
192
+ // only receives the stable code. (`retrieveAccount` throws bare token
193
+ // messages; the raw password values never appear in any error.)
194
+ console.error('users:changePassword failed:', error)
195
+ }
196
+ throw coded
197
+ }
171
198
  return null
172
199
  },
173
200
  })
@@ -3,9 +3,9 @@ import { Suspense, lazy } from 'react';
3
3
  import { Text } from '@elytracms/studio/ui';
4
4
  import { translate } from '@elytracms/studio/lib/i18n';
5
5
  /**
6
- * Lazy boundary for the AI chat panel (EC-199, EC-331 bundle discipline). The panel
7
- * and everything it imports — the creation orchestrator, `applyContentWithShape`, the
8
- * provider adapters (and the AI SDK behind the Anthropic one via dynamic import) — load
6
+ * Lazy boundary for the AI chat panel (EC-199, EC-331/EC-334 bundle discipline). The
7
+ * panel and everything it imports — `useChat` + the AI SDK (`ai` / `@ai-sdk/react`),
8
+ * the client tool executor, `applyContentWithShape`, the provider adapters — load
9
9
  * ONLY when a configured studio actually opens the panel. An unconfigured studio never
10
10
  * renders the toggle, never mounts this, and so pulls none of the AI chunk onto the CMS
11
11
  * critical path (pinned by the `lib/ai` bundle test). Same pattern as
@@ -1 +1 @@
1
- {"version":3,"file":"ai-chat-panel-lazy.js","sourceRoot":"","sources":["../../../src/components/ai/ai-chat-panel-lazy.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAEtD;;;;;;;;GAQG;AACH,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,CAChC,MAAM,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAC9E,CAAA;AAED,SAAS,eAAe;IACtB,OAAO,CACL,gBAAO,SAAS,EAAC,gFAAgF,YAC/F,cAAK,SAAS,EAAC,4CAA4C,YACzD,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,kBAAkB,CAAC,GACzB,GACH,GACA,CACT,CAAA;AACH,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,CACL,KAAC,QAAQ,IAAC,QAAQ,EAAE,KAAC,eAAe,KAAG,YACrC,KAAC,eAAe,KAAG,GACV,CACZ,CAAA;AACH,CAAC","sourcesContent":["import { Suspense, lazy } from 'react'\nimport { Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\n\n/**\n * Lazy boundary for the AI chat panel (EC-199, EC-331 bundle discipline). The panel\n * and everything it imports — the creation orchestrator, `applyContentWithShape`, the\n * provider adapters (and the AI SDK behind the Anthropic one via dynamic import) — load\n * ONLY when a configured studio actually opens the panel. An unconfigured studio never\n * renders the toggle, never mounts this, and so pulls none of the AI chunk onto the CMS\n * critical path (pinned by the `lib/ai` bundle test). Same pattern as\n * `version-compare-lazy` / `rich-text/lazy`.\n */\nconst AiChatPanelImpl = lazy(() =>\n import('./ai-chat-panel').then((module) => ({ default: module.AiChatPanel })),\n)\n\nfunction AiPanelFallback() {\n return (\n <aside className=\"flex h-full min-h-0 w-[360px] shrink-0 flex-col border-l border-border bg-card\">\n <div className=\"grid min-h-0 flex-1 place-items-center p-6\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('ai.panel.loading')}\n </Text>\n </div>\n </aside>\n )\n}\n\nexport function AiChatPanel() {\n return (\n <Suspense fallback={<AiPanelFallback />}>\n <AiChatPanelImpl />\n </Suspense>\n )\n}\n"]}
1
+ {"version":3,"file":"ai-chat-panel-lazy.js","sourceRoot":"","sources":["../../../src/components/ai/ai-chat-panel-lazy.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAEtD;;;;;;;;GAQG;AACH,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,CAChC,MAAM,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAC9E,CAAA;AAED,SAAS,eAAe;IACtB,OAAO,CACL,gBAAO,SAAS,EAAC,gFAAgF,YAC/F,cAAK,SAAS,EAAC,4CAA4C,YACzD,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,kBAAkB,CAAC,GACzB,GACH,GACA,CACT,CAAA;AACH,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,CACL,KAAC,QAAQ,IAAC,QAAQ,EAAE,KAAC,eAAe,KAAG,YACrC,KAAC,eAAe,KAAG,GACV,CACZ,CAAA;AACH,CAAC","sourcesContent":["import { Suspense, lazy } from 'react'\nimport { Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\n\n/**\n * Lazy boundary for the AI chat panel (EC-199, EC-331/EC-334 bundle discipline). The\n * panel and everything it imports — `useChat` + the AI SDK (`ai` / `@ai-sdk/react`),\n * the client tool executor, `applyContentWithShape`, the provider adapters — load\n * ONLY when a configured studio actually opens the panel. An unconfigured studio never\n * renders the toggle, never mounts this, and so pulls none of the AI chunk onto the CMS\n * critical path (pinned by the `lib/ai` bundle test). Same pattern as\n * `version-compare-lazy` / `rich-text/lazy`.\n */\nconst AiChatPanelImpl = lazy(() =>\n import('./ai-chat-panel').then((module) => ({ default: module.AiChatPanel })),\n)\n\nfunction AiPanelFallback() {\n return (\n <aside className=\"flex h-full min-h-0 w-[360px] shrink-0 flex-col border-l border-border bg-card\">\n <div className=\"grid min-h-0 flex-1 place-items-center p-6\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('ai.panel.loading')}\n </Text>\n </div>\n </aside>\n )\n}\n\nexport function AiChatPanel() {\n return (\n <Suspense fallback={<AiPanelFallback />}>\n <AiChatPanelImpl />\n </Suspense>\n )\n}\n"]}
@@ -1,10 +1 @@
1
- /**
2
- * The AI assistant chat panel (EC-199) — the right-hand collapsible surface in the CMS
3
- * area. It owns the chat state (the pure `chatReducer`), resolves the EC-331 provider
4
- * from `defineStudio({ ai })`, binds the narrow tools (search/list) to the operations
5
- * layer with the user's session, runs the creation orchestrator, and applies a
6
- * reviewed proposal as a DRAFT (operations upsert + a provenance-tagged version
7
- * snapshot). NEVER a modal (house rule). The whole module sits behind a lazy boundary
8
- * (`ai-chat-panel-lazy`), so an unconfigured studio pulls none of it — and no AI SDK.
9
- */
10
1
  export declare function AiChatPanel(): import("react").JSX.Element;
@@ -1,39 +1,63 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useMemo, useReducer, useState } from 'react';
3
- import { X } from 'lucide-react';
2
+ import { useCallback, useMemo, useReducer, useRef, useState } from 'react';
3
+ import { MessageSquarePlus, X } from 'lucide-react';
4
+ import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from 'ai';
5
+ import { useChat } from '@ai-sdk/react';
4
6
  import { createCollectionRegistry, resolveDocumentTitle, } from '@elytracms/core/cms-core';
5
7
  import { Button, Heading, Row, Spacer, Text } from '@elytracms/studio/ui';
6
8
  import { translate } from '@elytracms/studio/lib/i18n';
7
9
  import { STUDIO_PROJECT_ROUTE, studioPath, useNavigate } from '@elytracms/studio/router';
8
10
  import { resolveDeploymentAi } from '@elytracms/studio/lib/workspace';
9
11
  import { getStudioOperations } from '@elytracms/studio/lib/operations';
10
- import { AiRouteError, chatReducer, initialChatState, resolveAiConfig, resolveKnowledge, runCreateFlow, } from '@elytracms/studio/lib/ai';
12
+ import { DEFAULT_MAX_STEPS, buildChatContext, chatReviewReducer, clearConversation, createChatCandidateStore, createChatToolExecutor, initialChatReviewState, resolveAiConfig, resolveChatTarget, resolveKnowledge, withStepCap, } from '@elytracms/studio/lib/ai';
11
13
  import { useCms } from '../cms/cms-store.js';
12
14
  import { validateComposition, validateRichTextEmbeds } from '../cms/cms-composition-validators.js';
13
- import { AssistantTurn, Conversation, ConversationEmpty, PromptInput, ToolStatus, UserTurn, } from './chat-ui.js';
15
+ import { AssistantTurn, ChatErrorNote, Conversation, ConversationEmpty, PromptInput, ToolStatus, UserTurn, WorkingIndicator, describeToolPart, messageText, segmentMessageParts, } from './chat-ui.js';
14
16
  import { ProposalCard } from './proposal-card.js';
15
17
  import { setAiPanelOpen } from '@elytracms/studio/lib/ai-panel';
16
18
  /**
17
- * The AI assistant chat panel (EC-199) — the right-hand collapsible surface in the CMS
18
- * area. It owns the chat state (the pure `chatReducer`), resolves the EC-331 provider
19
- * from `defineStudio({ ai })`, binds the narrow tools (search/list) to the operations
20
- * layer with the user's session, runs the creation orchestrator, and applies a
21
- * reviewed proposal as a DRAFT (operations upsert + a provenance-tagged version
22
- * snapshot). NEVER a modal (house rule). The whole module sits behind a lazy boundary
23
- * (`ai-chat-panel-lazy`), so an unconfigured studio pulls none of it — and no AI SDK.
19
+ * The AI assistant chat panel (EC-199, moved onto the SDK's native protocol by
20
+ * EC-334) — the right-hand collapsible surface in the CMS area. `useChat` +
21
+ * `DefaultChatTransport` own the conversation against the route's native chat
22
+ * branch (visible token streaming); `onToolCall` dispatches into the client
23
+ * executor map (operations reads + terminal engines, all with the user's own
24
+ * session); `sendAutomaticallyWhen` is the SDK predicate wrapped with the
25
+ * per-turn step cap. What the SDK does not own lives in the pure review reducer:
26
+ * full proposals keyed by `toolCallId` (reviewed as a diff, applied as a DRAFT
27
+ * via operations upsert + a provenance-tagged snapshot — the EC-199 trust
28
+ * boundary, unmoved) and blocking errors. EC-380: the "new conversation"
29
+ * affordance clears busy-safely (stop → new chat id + review reset; pending
30
+ * proposals are discarded with reject semantics).
31
+ *
32
+ * NEVER a modal (house rule). The whole module (including `ai` + `@ai-sdk/react`)
33
+ * sits behind the lazy boundary (`ai-chat-panel-lazy`) — pinned in bundle tests.
24
34
  */
35
+ /** Adapt a `RouteHeaders` (static or thunk) to the transport's headers resolvable. */
36
+ function toTransportHeaders(headers) {
37
+ return async () => {
38
+ const resolved = typeof headers === 'function' ? await headers() : headers;
39
+ const record = {};
40
+ new Headers(resolved ?? {}).forEach((value, key) => {
41
+ record[key] = value;
42
+ });
43
+ return record;
44
+ };
45
+ }
25
46
  export function AiChatPanel() {
26
47
  const cms = useCms();
27
48
  const navigate = useNavigate();
28
- const [state, dispatch] = useReducer(chatReducer, initialChatState);
49
+ const [review, dispatch] = useReducer(chatReviewReducer, initialChatReviewState);
29
50
  const [input, setInput] = useState('');
51
+ // EC-380: the conversation counter — bumping it mints a fresh `useChat` id
52
+ // (a brand-new conversation) and a fresh relation-candidate store.
53
+ const [conversation, setConversation] = useState(1);
30
54
  const projectId = cms.projectId;
31
55
  const activeLocale = cms.activeLocale;
32
56
  // Resolve the AI platform config once (presence + capability gate). The panel only
33
57
  // mounts when configured, but a misconfigured provider still fails loud here.
34
58
  const resolved = useMemo(() => resolveAiConfig(resolveDeploymentAi()), []);
35
59
  const provider = resolved.resolved?.provider ?? null;
36
- const knowledgeSources = resolved.resolved?.knowledge ?? [];
60
+ const knowledgeSources = useMemo(() => resolved.resolved?.knowledge ?? [], [resolved]);
37
61
  const maxRepairAttempts = resolved.resolved?.limits?.maxRepairAttempts;
38
62
  const ready = provider !== null && resolved.ok && projectId !== undefined;
39
63
  const registry = useMemo(() => createCollectionRegistry(cms.collections), [cms.collections]);
@@ -43,7 +67,7 @@ export function AiChatPanel() {
43
67
  ...(doc.localized?.[activeLocale] ?? {}),
44
68
  }), [activeLocale]);
45
69
  /** Operations-backed tool readers — client-side, with the user's own session. */
46
- const tools = useMemo(() => {
70
+ const readers = useMemo(() => {
47
71
  const matches = (query, ...fields) => !query || fields.some((f) => f?.toLowerCase().includes(query.toLowerCase()));
48
72
  return {
49
73
  async searchDocuments({ collection, query }) {
@@ -74,7 +98,7 @@ export function AiChatPanel() {
74
98
  .map((asset) => ({ id: asset.id, title: asset.title ?? asset.filename }))
75
99
  .filter((r) => matches(query, r.title, r.id));
76
100
  },
77
- // EC-332: the translate flow loads a full document (draft values + localized).
101
+ // Full document (draft values + localized) — the getDocument/translate tools.
78
102
  async getDocument({ collection, id }) {
79
103
  if (projectId === undefined)
80
104
  return null;
@@ -110,53 +134,122 @@ export function AiChatPanel() {
110
134
  })
111
135
  .filter((item) => item.content.trim().length > 0);
112
136
  }, [projectId, cms, localeValues]);
113
- const runTurn = useCallback(async (text) => {
114
- if (!provider)
115
- return;
116
- try {
137
+ // The knowledge snapshot the executors read — refreshed by the body thunk on
138
+ // every request, so the terminal engines see what the model saw this turn.
139
+ const knowledgeRef = useRef({ items: [] });
140
+ // The chat transport target: the configured route provider's endpoint +
141
+ // session-header thunk (EC-371), re-evaluated per request.
142
+ const chatTarget = useMemo(() => resolveChatTarget(provider), [provider]);
143
+ const transport = useMemo(() => new DefaultChatTransport({
144
+ api: chatTarget.endpoint,
145
+ headers: toTransportHeaders(chatTarget.headers),
146
+ // The `body` THUNK — re-evaluated per request (fresh context every turn,
147
+ // 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`.
151
+ body: async () => {
117
152
  const knowledge = await resolveKnowledge(knowledgeSources, { readCollection });
118
- const result = await runCreateFlow({
119
- provider,
120
- tools,
121
- registry,
122
- collections: cms.collections,
123
- instruction: text,
124
- knowledge,
125
- locale: activeLocale,
126
- localeConfig: cms.localeConfig,
127
- mintDocumentId,
128
- validateRichTextEmbeds,
129
- validateComposition,
130
- ...(maxRepairAttempts !== undefined ? { maxRepairAttempts } : {}),
131
- onStep: (step) => dispatch({ type: 'step', step }),
132
- });
133
- dispatch({ type: 'result', result });
134
- }
135
- catch (error) {
136
- const message = error instanceof AiRouteError
137
- ? translate('ai.error.route')
138
- : translate('ai.error.generic');
139
- dispatch({ type: 'fail', message });
140
- }
153
+ knowledgeRef.current = knowledge;
154
+ return { context: buildChatContext({ collections: cms.collections, knowledge }) };
155
+ },
156
+ }), [chatTarget, knowledgeSources, readCollection, cms.collections]);
157
+ // Per-conversation relation-candidate store (EC-380: replaced on clear, so a
158
+ // fresh conversation cannot reference the previous one's searched ids).
159
+ const candidatesRef = useRef({
160
+ conversation,
161
+ store: createChatCandidateStore(),
162
+ });
163
+ if (candidatesRef.current.conversation !== conversation) {
164
+ candidatesRef.current = { conversation, store: createChatCandidateStore() };
165
+ }
166
+ const candidateStore = candidatesRef.current.store;
167
+ const executor = useMemo(() => {
168
+ if (!provider)
169
+ return null;
170
+ return createChatToolExecutor({
171
+ provider,
172
+ registry,
173
+ collections: cms.collections,
174
+ readers,
175
+ mintDocumentId,
176
+ candidates: candidateStore,
177
+ onProposal: (toolCallId, proposal) => dispatch({ type: 'propose', toolCallId, proposal }),
178
+ knowledge: () => knowledgeRef.current,
179
+ locale: activeLocale,
180
+ ...(cms.localeConfig ? { localeConfig: cms.localeConfig } : {}),
181
+ validateRichTextEmbeds,
182
+ validateComposition,
183
+ ...(maxRepairAttempts !== undefined ? { maxRepairAttempts } : {}),
184
+ });
141
185
  }, [
142
186
  provider,
143
- knowledgeSources,
144
- readCollection,
145
- tools,
146
187
  registry,
147
188
  cms.collections,
148
- activeLocale,
189
+ cms.localeConfig,
190
+ readers,
149
191
  mintDocumentId,
192
+ activeLocale,
150
193
  maxRepairAttempts,
194
+ candidateStore,
151
195
  ]);
196
+ const chat = useChat({
197
+ // A new conversation is a new chat id — `useChat` recreates its state.
198
+ id: `elytra-assistant-${conversation}`,
199
+ transport,
200
+ onToolCall: async ({ toolCall }) => {
201
+ if (!executor)
202
+ return;
203
+ const outcome = await executor.execute({
204
+ toolCallId: toolCall.toolCallId,
205
+ toolName: toolCall.toolName,
206
+ input: toolCall.input,
207
+ });
208
+ // Deliberately NOT awaited: `onToolCall` runs inside the chat's serial
209
+ // stream job, and `addToolOutput` enqueues on the same executor —
210
+ // awaiting it here deadlocks the stream (verified against ai@7.0.22).
211
+ if (outcome.ok) {
212
+ void chat.addToolOutput({
213
+ tool: toolCall.toolName,
214
+ toolCallId: toolCall.toolCallId,
215
+ output: outcome.output,
216
+ });
217
+ }
218
+ else {
219
+ void chat.addToolOutput({
220
+ state: 'output-error',
221
+ tool: toolCall.toolName,
222
+ toolCallId: toolCall.toolCallId,
223
+ errorText: outcome.errorText,
224
+ });
225
+ }
226
+ },
227
+ // The SDK predicate wrapped with the per-user-turn step cap: a capped turn
228
+ // stops cleanly with a visible note — never a silent stall (EC-334).
229
+ sendAutomaticallyWhen: withStepCap(lastAssistantMessageIsCompleteWithToolCalls, {
230
+ maxSteps: DEFAULT_MAX_STEPS,
231
+ onCap: () => dispatch({ type: 'fail', message: translate('ai.error.stepCap', { max: DEFAULT_MAX_STEPS }) }),
232
+ }),
233
+ onError: () => dispatch({ type: 'fail', message: translate('ai.error.route') }),
234
+ });
235
+ const busy = chat.status === 'submitted' || chat.status === 'streaming';
152
236
  const submit = useCallback(() => {
153
237
  const text = input.trim();
154
- if (text.length === 0 || state.status === 'busy' || !ready)
238
+ if (text.length === 0 || busy || !ready)
155
239
  return;
156
240
  setInput('');
157
- dispatch({ type: 'submit', text });
158
- void runTurn(text);
159
- }, [input, state.status, ready, runTurn]);
241
+ dispatch({ type: 'clear-error' });
242
+ void chat.sendMessage({ text });
243
+ }, [input, busy, ready, chat]);
244
+ // EC-380: clear / new topic — busy-safe (stop first), pending proposals
245
+ // discarded with reject semantics (the review state resets; nothing was applied).
246
+ const startNewConversation = useCallback(() => {
247
+ void clearConversation({ status: chat.status, stop: chat.stop }, () => {
248
+ dispatch({ type: 'clear' });
249
+ setInput('');
250
+ setConversation((current) => current + 1);
251
+ });
252
+ }, [chat.status, chat.stop]);
160
253
  const applyProposal = useCallback(async (proposal) => {
161
254
  if (projectId === undefined)
162
255
  return;
@@ -188,7 +281,7 @@ export function AiChatPanel() {
188
281
  }
189
282
  // Reflect in the session so the new draft shows in the list + is navigable.
190
283
  cms.saveDocument(doc);
191
- dispatch({ type: 'apply', proposalId: proposal.id });
284
+ dispatch({ type: 'apply', toolCallId: proposal.id });
192
285
  }, [projectId, cms]);
193
286
  const openProposal = useCallback((proposal) => {
194
287
  if (projectId === undefined)
@@ -198,14 +291,22 @@ export function AiChatPanel() {
198
291
  params: { projectId, _splat: studioPath.cmsDocument(proposal.collection, proposal.documentId) },
199
292
  });
200
293
  }, [navigate, projectId]);
201
- return (_jsxs("aside", { className: "flex h-full min-h-0 w-[360px] shrink-0 flex-col border-l border-border bg-card", children: [_jsxs(Row, { gap: "2", align: "center", className: "h-12 shrink-0 border-b border-border px-3", children: [_jsx(Heading, { level: 2, size: "h4", children: translate('ai.panel.title') }), _jsx(Spacer, {}), _jsx(Button, { size: "icon-sm", variant: "ghost", onClick: () => setAiPanelOpen(false), title: translate('ai.panel.close'), "aria-label": translate('ai.panel.close'), children: _jsx(X, {}) })] }), _jsxs(Conversation, { atBottomKey: state.messages, children: [state.messages.length === 0 ? _jsx(ConversationEmpty, {}) : null, state.messages.map((message) => {
202
- if (message.role === 'user')
203
- return _jsx(UserTurn, { text: message.text }, message.id);
204
- if (message.kind === 'text')
205
- return (_jsx(AssistantTurn, { children: _jsx(Text, { size: "sm", className: "whitespace-pre-wrap break-words", children: message.text }) }, message.id));
206
- if (message.kind === 'activity')
207
- return (_jsx(AssistantTurn, { children: _jsx(ToolStatus, { steps: message.steps, active: state.status === 'busy' && state.activityId === message.id }) }, message.id));
208
- return (_jsx(ProposalCard, { proposal: message.proposal, onApply: () => void applyProposal(message.proposal), onDiscard: () => dispatch({ type: 'reject', proposalId: message.proposal.id }), onOpen: () => openProposal(message.proposal) }, message.id));
209
- })] }), ready ? (_jsx(PromptInput, { value: input, onChange: setInput, onSubmit: submit, disabled: state.status === 'busy' })) : (_jsx("div", { className: "border-t border-border p-3", children: _jsx(Text, { size: "xs", tone: "muted", children: translate('ai.panel.unavailable') }) }))] }));
294
+ const hasProposal = useCallback((toolCallId) => review.proposals[toolCallId] !== undefined, [review.proposals]);
295
+ const renderAssistantMessage = (message) => {
296
+ const segments = segmentMessageParts(message.parts, hasProposal);
297
+ return segments.map((segment, index) => {
298
+ if (segment.kind === 'text') {
299
+ return (_jsx(AssistantTurn, { children: _jsx(Text, { size: "sm", className: "whitespace-pre-wrap break-words", children: segment.text }) }, `${message.id}-${index}`));
300
+ }
301
+ if (segment.kind === 'tools') {
302
+ return (_jsx(AssistantTurn, { children: _jsx(ToolStatus, { steps: segment.parts.map(describeToolPart) }) }, `${message.id}-${index}`));
303
+ }
304
+ const proposal = review.proposals[segment.toolCallId];
305
+ if (!proposal)
306
+ return null;
307
+ return (_jsx(ProposalCard, { proposal: proposal, onApply: () => void applyProposal(proposal), onDiscard: () => dispatch({ type: 'reject', toolCallId: proposal.id }), onOpen: () => openProposal(proposal) }, segment.toolCallId));
308
+ });
309
+ };
310
+ return (_jsxs("aside", { className: "flex h-full min-h-0 w-[360px] shrink-0 flex-col border-l border-border bg-card", children: [_jsxs(Row, { gap: "2", align: "center", className: "h-12 shrink-0 border-b border-border px-3", children: [_jsx(Heading, { level: 2, size: "h4", children: translate('ai.panel.title') }), _jsx(Spacer, {}), _jsx(Button, { size: "icon-sm", variant: "ghost", onClick: startNewConversation, disabled: chat.messages.length === 0 && review.error === null && !busy, title: translate('ai.panel.newConversation'), "aria-label": translate('ai.panel.newConversation'), children: _jsx(MessageSquarePlus, {}) }), _jsx(Button, { size: "icon-sm", variant: "ghost", onClick: () => setAiPanelOpen(false), title: translate('ai.panel.close'), "aria-label": translate('ai.panel.close'), children: _jsx(X, {}) })] }), _jsxs(Conversation, { atBottomKey: chat.messages, children: [chat.messages.length === 0 ? _jsx(ConversationEmpty, {}) : null, chat.messages.map((message) => message.role === 'user' ? (_jsx(UserTurn, { text: messageText(message) }, message.id)) : (_jsx("div", { className: "contents", children: renderAssistantMessage(message) }, message.id))), chat.status === 'submitted' ? (_jsx(AssistantTurn, { children: _jsx(WorkingIndicator, {}) })) : null, review.error !== null ? (_jsx(AssistantTurn, { children: _jsx(ChatErrorNote, { message: review.error }) })) : null] }), ready ? (_jsx(PromptInput, { value: input, onChange: setInput, onSubmit: submit, disabled: busy })) : (_jsx("div", { className: "border-t border-border p-3", children: _jsx(Text, { size: "xs", tone: "muted", children: translate('ai.panel.unavailable') }) }))] }));
210
311
  }
211
312
  //# sourceMappingURL=ai-chat-panel.js.map
@@ -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, useState } from 'react'\nimport { X } from 'lucide-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 AiRouteError,\n chatReducer,\n initialChatState,\n resolveAiConfig,\n resolveKnowledge,\n runCreateFlow,\n} from '@elytracms/studio/lib/ai'\nimport type {\n CreateFlowStep,\n CreateFlowTools,\n KnowledgeCollectionReader,\n ProposalReview,\n} from '@elytracms/studio/lib/ai'\nimport { useCms } from '../cms/cms-store'\nimport { validateComposition, validateRichTextEmbeds } from '../cms/cms-composition-validators'\nimport {\n AssistantTurn,\n Conversation,\n ConversationEmpty,\n PromptInput,\n ToolStatus,\n UserTurn,\n} 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) — the right-hand collapsible surface in the CMS\n * area. It owns the chat state (the pure `chatReducer`), resolves the EC-331 provider\n * from `defineStudio({ ai })`, binds the narrow tools (search/list) to the operations\n * layer with the user's session, runs the creation orchestrator, and applies a\n * reviewed proposal as a DRAFT (operations upsert + a provenance-tagged version\n * snapshot). NEVER a modal (house rule). The whole module sits behind a lazy boundary\n * (`ai-chat-panel-lazy`), so an unconfigured studio pulls none of it — and no AI SDK.\n */\nexport function AiChatPanel() {\n const cms = useCms()\n const navigate = useNavigate()\n const [state, dispatch] = useReducer(chatReducer, initialChatState)\n const [input, setInput] = useState('')\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 = resolved.resolved?.knowledge ?? []\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 tools = useMemo<CreateFlowTools>(() => {\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 // EC-332: the translate flow loads a full document (draft values + localized).\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 const runTurn = useCallback(\n async (text: string) => {\n if (!provider) return\n try {\n const knowledge = await resolveKnowledge(knowledgeSources, { readCollection })\n const result = await runCreateFlow({\n provider,\n tools,\n registry,\n collections: cms.collections,\n instruction: text,\n knowledge,\n locale: activeLocale,\n localeConfig: cms.localeConfig,\n mintDocumentId,\n validateRichTextEmbeds,\n validateComposition,\n ...(maxRepairAttempts !== undefined ? { maxRepairAttempts } : {}),\n onStep: (step: CreateFlowStep) => dispatch({ type: 'step', step }),\n })\n dispatch({ type: 'result', result })\n } catch (error) {\n const message =\n error instanceof AiRouteError\n ? translate('ai.error.route')\n : translate('ai.error.generic')\n dispatch({ type: 'fail', message })\n }\n },\n [\n provider,\n knowledgeSources,\n readCollection,\n tools,\n registry,\n cms.collections,\n activeLocale,\n mintDocumentId,\n maxRepairAttempts,\n ],\n )\n\n const submit = useCallback(() => {\n const text = input.trim()\n if (text.length === 0 || state.status === 'busy' || !ready) return\n setInput('')\n dispatch({ type: 'submit', text })\n void runTurn(text)\n }, [input, state.status, ready, runTurn])\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', proposalId: 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 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={() => 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={state.messages}>\n {state.messages.length === 0 ? <ConversationEmpty /> : null}\n {state.messages.map((message) => {\n if (message.role === 'user') return <UserTurn key={message.id} text={message.text} />\n if (message.kind === 'text')\n return (\n <AssistantTurn key={message.id}>\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {message.text}\n </Text>\n </AssistantTurn>\n )\n if (message.kind === 'activity')\n return (\n <AssistantTurn key={message.id}>\n <ToolStatus\n steps={message.steps}\n active={state.status === 'busy' && state.activityId === message.id}\n />\n </AssistantTurn>\n )\n return (\n <ProposalCard\n key={message.id}\n proposal={message.proposal}\n onApply={() => void applyProposal(message.proposal)}\n onDiscard={() => dispatch({ type: 'reject', proposalId: message.proposal.id })}\n onOpen={() => openProposal(message.proposal)}\n />\n )\n })}\n </Conversation>\n\n {ready ? (\n <PromptInput value={input} onChange={setInput} onSubmit={submit} disabled={state.status === '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 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"]}