@noodleseed/assistant 1.21.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +47 -9
  2. package/dist/app-view-element-1i_w6FDf.d.cts +40 -0
  3. package/dist/app-view-element-7ZQZLYbL.d.ts +40 -0
  4. package/dist/app-view.cjs +18666 -0
  5. package/dist/app-view.cjs.map +1 -0
  6. package/dist/app-view.d.cts +9 -0
  7. package/dist/app-view.d.ts +9 -0
  8. package/dist/app-view.js +15 -0
  9. package/dist/app-view.js.map +1 -0
  10. package/dist/chunk-5NBHHNI3.js +180 -0
  11. package/dist/chunk-5NBHHNI3.js.map +1 -0
  12. package/dist/{chunk-ZPZHPXIQ.js → chunk-IBDP6PXQ.js} +9 -3882
  13. package/dist/chunk-IBDP6PXQ.js.map +1 -0
  14. package/dist/chunk-P223CYPO.js +3880 -0
  15. package/dist/chunk-P223CYPO.js.map +1 -0
  16. package/dist/{chunk-LEKEIHJV.js → chunk-X7UQDIGS.js} +824 -4049
  17. package/dist/chunk-X7UQDIGS.js.map +1 -0
  18. package/dist/chunk-XKTUXFGX.js +3323 -0
  19. package/dist/chunk-XKTUXFGX.js.map +1 -0
  20. package/dist/{client-BkJlpN5N.d.ts → client-Bp_VKpMg.d.ts} +1 -1
  21. package/dist/{client-BZ7xPwfW.d.cts → client-uo57Swlm.d.cts} +1 -1
  22. package/dist/client.d.cts +1 -1
  23. package/dist/client.d.ts +1 -1
  24. package/dist/client.js +2 -1
  25. package/dist/embed.global.js +90 -75
  26. package/dist/index.cjs +111 -24
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +2 -2
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +4 -2
  31. package/dist/index.js.map +1 -1
  32. package/dist/react/client.d.cts +1 -1
  33. package/dist/react/client.d.ts +1 -1
  34. package/dist/react/client.js +2 -1
  35. package/dist/react/client.js.map +1 -1
  36. package/dist/react.cjs +299 -56
  37. package/dist/react.cjs.map +1 -1
  38. package/dist/react.d.cts +7 -8
  39. package/dist/react.d.ts +7 -8
  40. package/dist/react.js +31 -41
  41. package/dist/react.js.map +1 -1
  42. package/package.json +16 -2
  43. package/dist/chunk-LEKEIHJV.js.map +0 -1
  44. package/dist/chunk-ZPZHPXIQ.js.map +0 -1
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { d as AssistantViewAvailableDetail, f as AssistantClientEvent, a as AssistantContext, e as AssistantModelContextUpdate, A as AssistantPageContext, h as AssistantInteractionResponse } from './client-BZ7xPwfW.cjs';
2
- export { g as AssistantErrorDetail, i as AssistantEvent, j as AssistantViewAvailableEvent } from './client-BZ7xPwfW.cjs';
1
+ import { a as AssistantViewAvailableDetail, g as AssistantClientEvent, c as AssistantContext, f as AssistantModelContextUpdate, b as AssistantPageContext, h as AssistantInteractionResponse } from './client-uo57Swlm.cjs';
2
+ export { e as AssistantErrorDetail, i as AssistantEvent, j as AssistantViewAvailableEvent } from './client-uo57Swlm.cjs';
3
3
  import './appearance-Cc-eqSbp.cjs';
4
4
  import 'ai';
5
5
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { d as AssistantViewAvailableDetail, f as AssistantClientEvent, a as AssistantContext, e as AssistantModelContextUpdate, A as AssistantPageContext, h as AssistantInteractionResponse } from './client-BkJlpN5N.js';
2
- export { g as AssistantErrorDetail, i as AssistantEvent, j as AssistantViewAvailableEvent } from './client-BkJlpN5N.js';
1
+ import { a as AssistantViewAvailableDetail, g as AssistantClientEvent, c as AssistantContext, f as AssistantModelContextUpdate, b as AssistantPageContext, h as AssistantInteractionResponse } from './client-Bp_VKpMg.js';
2
+ export { e as AssistantErrorDetail, i as AssistantEvent, j as AssistantViewAvailableEvent } from './client-Bp_VKpMg.js';
3
3
  import './appearance-Cc-eqSbp.js';
4
4
  import 'ai';
5
5
 
package/dist/index.js CHANGED
@@ -2,8 +2,10 @@ import {
2
2
  ASSISTANT_TAG_NAME,
3
3
  NoodleAssistantElement,
4
4
  registerNoodleAssistant
5
- } from "./chunk-LEKEIHJV.js";
6
- import "./chunk-ZPZHPXIQ.js";
5
+ } from "./chunk-X7UQDIGS.js";
6
+ import "./chunk-XKTUXFGX.js";
7
+ import "./chunk-IBDP6PXQ.js";
8
+ import "./chunk-P223CYPO.js";
7
9
  import "./chunk-3YYTUJPJ.js";
8
10
  import "./chunk-5FUTL2UF.js";
9
11
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export {\n ASSISTANT_TAG_NAME,\n NoodleAssistantElement,\n registerNoodleAssistant,\n} from './element.js';\nexport type { AssistantViewAvailableDetail, AssistantViewAvailableEvent } from './events.js';\nexport type {\n AssistantAppearance,\n AssistantAppearanceTheme,\n AssistantAppearanceWarning,\n AssistantSurfaceAppearance,\n} from './host-appearance.js';\nexport type { AssistantErrorDetail, AssistantEvent } from './transport.js';\n\nimport { registerNoodleAssistant } from './element.js';\n\nregisterNoodleAssistant();\n"],"mappings":";;;;;;;;;;AAgBA,wBAAwB;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export {\n ASSISTANT_TAG_NAME,\n NoodleAssistantElement,\n registerNoodleAssistant,\n} from './element.js';\nexport type { AssistantViewAvailableDetail, AssistantViewAvailableEvent } from './events.js';\nexport type {\n AssistantAppearance,\n AssistantAppearanceTheme,\n AssistantAppearanceWarning,\n AssistantSurfaceAppearance,\n} from './host-appearance.js';\nexport type { AssistantErrorDetail, AssistantEvent } from './transport.js';\n\nimport { registerNoodleAssistant } from './element.js';\n\nregisterNoodleAssistant();\n"],"mappings":";;;;;;;;;;;;AAgBA,wBAAwB;","names":[]}
@@ -1,4 +1,4 @@
1
- import { A as AssistantPageContext, a as AssistantContext, C as CreateAssistantClientOptions, b as AssistantChatState, c as AssistantClient } from '../client-BZ7xPwfW.cjs';
1
+ import { b as AssistantPageContext, c as AssistantContext, C as CreateAssistantClientOptions, d as AssistantChatState, A as AssistantClient } from '../client-uo57Swlm.cjs';
2
2
  import '../appearance-Cc-eqSbp.cjs';
3
3
  import 'ai';
4
4
 
@@ -1,4 +1,4 @@
1
- import { A as AssistantPageContext, a as AssistantContext, C as CreateAssistantClientOptions, b as AssistantChatState, c as AssistantClient } from '../client-BkJlpN5N.js';
1
+ import { b as AssistantPageContext, c as AssistantContext, C as CreateAssistantClientOptions, d as AssistantChatState, A as AssistantClient } from '../client-Bp_VKpMg.js';
2
2
  import '../appearance-Cc-eqSbp.js';
3
3
  import 'ai';
4
4
 
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import {
3
3
  createAssistantClient
4
- } from "../chunk-ZPZHPXIQ.js";
4
+ } from "../chunk-IBDP6PXQ.js";
5
+ import "../chunk-P223CYPO.js";
5
6
  import "../chunk-3YYTUJPJ.js";
6
7
  import "../chunk-5FUTL2UF.js";
7
8
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react-client.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport {\n type AssistantChatState,\n type AssistantClient,\n type AssistantContext,\n type AssistantModelContextUpdate,\n type AssistantPageContext,\n type CreateAssistantClientOptions,\n createAssistantClient,\n} from './client.js';\n\nexport type UseNoodleAssistantOptions<\n TPageContext extends AssistantPageContext = AssistantContext,\n> = CreateAssistantClientOptions<TPageContext> & {\n /**\n * Browser-local identity for the authenticated principal/tenant.\n * Changing it discards the previous in-memory session and transcript; it is never sent to Noodle.\n */\n readonly principalKey: string;\n};\n\nexport type UseNoodleAssistantResult<TPageContext extends AssistantPageContext = AssistantContext> =\n AssistantChatState & {\n /** Canonical controller for turns, interactions, context, Apps requests, and aborts. */\n readonly client: AssistantClient<TPageContext>;\n };\n\ninterface AssistantReactStore {\n readonly getSnapshot: () => AssistantChatState;\n readonly subscribe: (listener: () => void) => () => void;\n}\n\nconst useCommittedLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;\n\n/**\n * Subscribe a customer-owned React renderer to the DOM-free assistant client.\n *\n * The hook owns only client lifetime and React state projection. Session exchange, transport,\n * retries, interaction continuations, and transcript construction remain client responsibilities.\n */\nexport function useNoodleAssistant<TPageContext extends AssistantPageContext = AssistantContext>(\n options: UseNoodleAssistantOptions<TPageContext>,\n): UseNoodleAssistantResult<TPageContext> {\n const optionsRef = useRef(options);\n const { fetch: injectedFetch, principalKey, sessionEndpoint, embedId, serviceUrl } = options;\n\n const client = useMemo(() => {\n if (!principalKey.trim()) throw new Error('principalKey is required');\n // The two mounts are spelled out rather than spread from one object: the exclusive union is what\n // stops a caller passing both, and a spread would collapse it back into an ambiguous shape.\n const behavior = {\n ...(injectedFetch ? { fetch: injectedFetch } : {}),\n clientContext: () => readProvider(optionsRef.current.clientContext),\n pageContext: () => readProvider(optionsRef.current.pageContext),\n };\n return embedId !== undefined\n ? createAssistantClient<TPageContext>({\n embedId,\n ...(serviceUrl !== undefined ? { serviceUrl } : {}),\n ...behavior,\n })\n : createAssistantClient<TPageContext>({\n sessionEndpoint: sessionEndpoint ?? '',\n ...behavior,\n });\n }, [injectedFetch, principalKey, sessionEndpoint, embedId, serviceUrl]);\n const store = useMemo(() => createAssistantReactStore(client), [client]);\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);\n const synchronized = useRef<\n | {\n readonly client: AssistantClient<TPageContext>;\n readonly context: AssistantContext | undefined;\n readonly modelContext: AssistantModelContextUpdate | undefined;\n }\n | undefined\n >(undefined);\n\n useCommittedLayoutEffect(() => {\n optionsRef.current = options;\n const previous = synchronized.current;\n if (previous?.client === client) {\n if (previous.context !== options.context) {\n client.updateContext(options.context ?? {});\n }\n if (previous.modelContext !== options.modelContext) {\n client.updateModelContext(options.modelContext ?? {});\n }\n } else {\n if (options.context !== undefined) client.updateContext(options.context);\n if (options.modelContext !== undefined) client.updateModelContext(options.modelContext);\n }\n synchronized.current = {\n client,\n context: options.context,\n modelContext: options.modelContext,\n };\n }, [client, options]);\n\n useCommittedLayoutEffect(\n () => () => {\n client.abort();\n client.resetSession();\n },\n [client],\n );\n\n return useMemo(() => ({ client, ...state }), [client, state]);\n}\n\nfunction createAssistantReactStore<TPageContext extends AssistantPageContext>(\n client: AssistantClient<TPageContext>,\n): AssistantReactStore {\n let snapshot = client.getChatState();\n return {\n getSnapshot: () => snapshot,\n subscribe(listener) {\n let subscribing = true;\n const unsubscribe = client.subscribeChat((next) => {\n snapshot = next;\n if (!subscribing) listener();\n });\n subscribing = false;\n return unsubscribe;\n },\n };\n}\n\nfunction readProvider<T>(value: T | (() => T | undefined) | undefined): T | undefined {\n return typeof value === 'function' ? (value as () => T | undefined)() : value;\n}\n"],"mappings":";;;;;;;;AAEA,SAAS,WAAW,iBAAiB,SAAS,QAAQ,4BAA4B;AAgClF,IAAM,2BAA2B,OAAO,WAAW,cAAc,YAAY;AAQtE,SAAS,mBACd,SACwC;AACxC,QAAM,aAAa,OAAO,OAAO;AACjC,QAAM,EAAE,OAAO,eAAe,cAAc,iBAAiB,SAAS,WAAW,IAAI;AAErF,QAAM,SAAS,QAAQ,MAAM;AAC3B,QAAI,CAAC,aAAa,KAAK,EAAG,OAAM,IAAI,MAAM,0BAA0B;AAGpE,UAAM,WAAW;AAAA,MACf,GAAI,gBAAgB,EAAE,OAAO,cAAc,IAAI,CAAC;AAAA,MAChD,eAAe,MAAM,aAAa,WAAW,QAAQ,aAAa;AAAA,MAClE,aAAa,MAAM,aAAa,WAAW,QAAQ,WAAW;AAAA,IAChE;AACA,WAAO,YAAY,SACf,sBAAoC;AAAA,MAClC;AAAA,MACA,GAAI,eAAe,SAAY,EAAE,WAAW,IAAI,CAAC;AAAA,MACjD,GAAG;AAAA,IACL,CAAC,IACD,sBAAoC;AAAA,MAClC,iBAAiB,mBAAmB;AAAA,MACpC,GAAG;AAAA,IACL,CAAC;AAAA,EACP,GAAG,CAAC,eAAe,cAAc,iBAAiB,SAAS,UAAU,CAAC;AACtE,QAAM,QAAQ,QAAQ,MAAM,0BAA0B,MAAM,GAAG,CAAC,MAAM,CAAC;AACvE,QAAM,QAAQ,qBAAqB,MAAM,WAAW,MAAM,aAAa,MAAM,WAAW;AACxF,QAAM,eAAe,OAOnB,MAAS;AAEX,2BAAyB,MAAM;AAC7B,eAAW,UAAU;AACrB,UAAM,WAAW,aAAa;AAC9B,QAAI,UAAU,WAAW,QAAQ;AAC/B,UAAI,SAAS,YAAY,QAAQ,SAAS;AACxC,eAAO,cAAc,QAAQ,WAAW,CAAC,CAAC;AAAA,MAC5C;AACA,UAAI,SAAS,iBAAiB,QAAQ,cAAc;AAClD,eAAO,mBAAmB,QAAQ,gBAAgB,CAAC,CAAC;AAAA,MACtD;AAAA,IACF,OAAO;AACL,UAAI,QAAQ,YAAY,OAAW,QAAO,cAAc,QAAQ,OAAO;AACvE,UAAI,QAAQ,iBAAiB,OAAW,QAAO,mBAAmB,QAAQ,YAAY;AAAA,IACxF;AACA,iBAAa,UAAU;AAAA,MACrB;AAAA,MACA,SAAS,QAAQ;AAAA,MACjB,cAAc,QAAQ;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,QAAQ,OAAO,CAAC;AAEpB;AAAA,IACE,MAAM,MAAM;AACV,aAAO,MAAM;AACb,aAAO,aAAa;AAAA,IACtB;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,SAAO,QAAQ,OAAO,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,KAAK,CAAC;AAC9D;AAEA,SAAS,0BACP,QACqB;AACrB,MAAI,WAAW,OAAO,aAAa;AACnC,SAAO;AAAA,IACL,aAAa,MAAM;AAAA,IACnB,UAAU,UAAU;AAClB,UAAI,cAAc;AAClB,YAAM,cAAc,OAAO,cAAc,CAAC,SAAS;AACjD,mBAAW;AACX,YAAI,CAAC,YAAa,UAAS;AAAA,MAC7B,CAAC;AACD,oBAAc;AACd,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEA,SAAS,aAAgB,OAA6D;AACpF,SAAO,OAAO,UAAU,aAAc,MAA8B,IAAI;AAC1E;","names":[]}
1
+ {"version":3,"sources":["../../src/react-client.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport {\n type AssistantChatState,\n type AssistantClient,\n type AssistantContext,\n type AssistantModelContextUpdate,\n type AssistantPageContext,\n type CreateAssistantClientOptions,\n createAssistantClient,\n} from './client.js';\n\nexport type UseNoodleAssistantOptions<\n TPageContext extends AssistantPageContext = AssistantContext,\n> = CreateAssistantClientOptions<TPageContext> & {\n /**\n * Browser-local identity for the authenticated principal/tenant.\n * Changing it discards the previous in-memory session and transcript; it is never sent to Noodle.\n */\n readonly principalKey: string;\n};\n\nexport type UseNoodleAssistantResult<TPageContext extends AssistantPageContext = AssistantContext> =\n AssistantChatState & {\n /** Canonical controller for turns, interactions, context, Apps requests, and aborts. */\n readonly client: AssistantClient<TPageContext>;\n };\n\ninterface AssistantReactStore {\n readonly getSnapshot: () => AssistantChatState;\n readonly subscribe: (listener: () => void) => () => void;\n}\n\nconst useCommittedLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;\n\n/**\n * Subscribe a customer-owned React renderer to the DOM-free assistant client.\n *\n * The hook owns only client lifetime and React state projection. Session exchange, transport,\n * retries, interaction continuations, and transcript construction remain client responsibilities.\n */\nexport function useNoodleAssistant<TPageContext extends AssistantPageContext = AssistantContext>(\n options: UseNoodleAssistantOptions<TPageContext>,\n): UseNoodleAssistantResult<TPageContext> {\n const optionsRef = useRef(options);\n const { fetch: injectedFetch, principalKey, sessionEndpoint, embedId, serviceUrl } = options;\n\n const client = useMemo(() => {\n if (!principalKey.trim()) throw new Error('principalKey is required');\n // The two mounts are spelled out rather than spread from one object: the exclusive union is what\n // stops a caller passing both, and a spread would collapse it back into an ambiguous shape.\n const behavior = {\n ...(injectedFetch ? { fetch: injectedFetch } : {}),\n clientContext: () => readProvider(optionsRef.current.clientContext),\n pageContext: () => readProvider(optionsRef.current.pageContext),\n };\n return embedId !== undefined\n ? createAssistantClient<TPageContext>({\n embedId,\n ...(serviceUrl !== undefined ? { serviceUrl } : {}),\n ...behavior,\n })\n : createAssistantClient<TPageContext>({\n sessionEndpoint: sessionEndpoint ?? '',\n ...behavior,\n });\n }, [injectedFetch, principalKey, sessionEndpoint, embedId, serviceUrl]);\n const store = useMemo(() => createAssistantReactStore(client), [client]);\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);\n const synchronized = useRef<\n | {\n readonly client: AssistantClient<TPageContext>;\n readonly context: AssistantContext | undefined;\n readonly modelContext: AssistantModelContextUpdate | undefined;\n }\n | undefined\n >(undefined);\n\n useCommittedLayoutEffect(() => {\n optionsRef.current = options;\n const previous = synchronized.current;\n if (previous?.client === client) {\n if (previous.context !== options.context) {\n client.updateContext(options.context ?? {});\n }\n if (previous.modelContext !== options.modelContext) {\n client.updateModelContext(options.modelContext ?? {});\n }\n } else {\n if (options.context !== undefined) client.updateContext(options.context);\n if (options.modelContext !== undefined) client.updateModelContext(options.modelContext);\n }\n synchronized.current = {\n client,\n context: options.context,\n modelContext: options.modelContext,\n };\n }, [client, options]);\n\n useCommittedLayoutEffect(\n () => () => {\n client.abort();\n client.resetSession();\n },\n [client],\n );\n\n return useMemo(() => ({ client, ...state }), [client, state]);\n}\n\nfunction createAssistantReactStore<TPageContext extends AssistantPageContext>(\n client: AssistantClient<TPageContext>,\n): AssistantReactStore {\n let snapshot = client.getChatState();\n return {\n getSnapshot: () => snapshot,\n subscribe(listener) {\n let subscribing = true;\n const unsubscribe = client.subscribeChat((next) => {\n snapshot = next;\n if (!subscribing) listener();\n });\n subscribing = false;\n return unsubscribe;\n },\n };\n}\n\nfunction readProvider<T>(value: T | (() => T | undefined) | undefined): T | undefined {\n return typeof value === 'function' ? (value as () => T | undefined)() : value;\n}\n"],"mappings":";;;;;;;;;AAEA,SAAS,WAAW,iBAAiB,SAAS,QAAQ,4BAA4B;AAgClF,IAAM,2BAA2B,OAAO,WAAW,cAAc,YAAY;AAQtE,SAAS,mBACd,SACwC;AACxC,QAAM,aAAa,OAAO,OAAO;AACjC,QAAM,EAAE,OAAO,eAAe,cAAc,iBAAiB,SAAS,WAAW,IAAI;AAErF,QAAM,SAAS,QAAQ,MAAM;AAC3B,QAAI,CAAC,aAAa,KAAK,EAAG,OAAM,IAAI,MAAM,0BAA0B;AAGpE,UAAM,WAAW;AAAA,MACf,GAAI,gBAAgB,EAAE,OAAO,cAAc,IAAI,CAAC;AAAA,MAChD,eAAe,MAAM,aAAa,WAAW,QAAQ,aAAa;AAAA,MAClE,aAAa,MAAM,aAAa,WAAW,QAAQ,WAAW;AAAA,IAChE;AACA,WAAO,YAAY,SACf,sBAAoC;AAAA,MAClC;AAAA,MACA,GAAI,eAAe,SAAY,EAAE,WAAW,IAAI,CAAC;AAAA,MACjD,GAAG;AAAA,IACL,CAAC,IACD,sBAAoC;AAAA,MAClC,iBAAiB,mBAAmB;AAAA,MACpC,GAAG;AAAA,IACL,CAAC;AAAA,EACP,GAAG,CAAC,eAAe,cAAc,iBAAiB,SAAS,UAAU,CAAC;AACtE,QAAM,QAAQ,QAAQ,MAAM,0BAA0B,MAAM,GAAG,CAAC,MAAM,CAAC;AACvE,QAAM,QAAQ,qBAAqB,MAAM,WAAW,MAAM,aAAa,MAAM,WAAW;AACxF,QAAM,eAAe,OAOnB,MAAS;AAEX,2BAAyB,MAAM;AAC7B,eAAW,UAAU;AACrB,UAAM,WAAW,aAAa;AAC9B,QAAI,UAAU,WAAW,QAAQ;AAC/B,UAAI,SAAS,YAAY,QAAQ,SAAS;AACxC,eAAO,cAAc,QAAQ,WAAW,CAAC,CAAC;AAAA,MAC5C;AACA,UAAI,SAAS,iBAAiB,QAAQ,cAAc;AAClD,eAAO,mBAAmB,QAAQ,gBAAgB,CAAC,CAAC;AAAA,MACtD;AAAA,IACF,OAAO;AACL,UAAI,QAAQ,YAAY,OAAW,QAAO,cAAc,QAAQ,OAAO;AACvE,UAAI,QAAQ,iBAAiB,OAAW,QAAO,mBAAmB,QAAQ,YAAY;AAAA,IACxF;AACA,iBAAa,UAAU;AAAA,MACrB;AAAA,MACA,SAAS,QAAQ;AAAA,MACjB,cAAc,QAAQ;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,QAAQ,OAAO,CAAC;AAEpB;AAAA,IACE,MAAM,MAAM;AACV,aAAO,MAAM;AACb,aAAO,aAAa;AAAA,IACtB;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,SAAO,QAAQ,OAAO,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,KAAK,CAAC;AAC9D;AAEA,SAAS,0BACP,QACqB;AACrB,MAAI,WAAW,OAAO,aAAa;AACnC,SAAO;AAAA,IACL,aAAa,MAAM;AAAA,IACnB,UAAU,UAAU;AAClB,UAAI,cAAc;AAClB,YAAM,cAAc,OAAO,cAAc,CAAC,SAAS;AACjD,mBAAW;AACX,YAAI,CAAC,YAAa,UAAS;AAAA,MAC7B,CAAC;AACD,oBAAc;AACd,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEA,SAAS,aAAgB,OAA6D;AACpF,SAAO,OAAO,UAAU,aAAc,MAA8B,IAAI;AAC1E;","names":[]}
package/dist/react.cjs CHANGED
@@ -22112,11 +22112,11 @@ function isAllowedAppLink(value, allowedDomains, baseUrl) {
22112
22112
  return void 0;
22113
22113
  }
22114
22114
  }
22115
- function createAssistantAppHostContext(theme) {
22115
+ function createAssistantAppHostContext(theme, allowFullscreen = false, displayMode = "inline") {
22116
22116
  return {
22117
22117
  theme,
22118
- displayMode: "inline",
22119
- availableDisplayModes: ["inline", "fullscreen"],
22118
+ displayMode: allowFullscreen && displayMode === "fullscreen" ? "fullscreen" : "inline",
22119
+ availableDisplayModes: allowFullscreen ? ["inline", "fullscreen"] : ["inline"],
22120
22120
  platform: "web"
22121
22121
  };
22122
22122
  }
@@ -22142,6 +22142,13 @@ function mountAssistantApp(detail, actions, options = {}) {
22142
22142
  title.textContent = detail.title;
22143
22143
  card.append(title);
22144
22144
  }
22145
+ const exitFullscreen = document.createElement("button");
22146
+ exitFullscreen.className = "noodle-app-fullscreen-exit";
22147
+ exitFullscreen.type = "button";
22148
+ exitFullscreen.setAttribute("aria-label", "Exit fullscreen");
22149
+ exitFullscreen.title = "Exit fullscreen";
22150
+ exitFullscreen.hidden = true;
22151
+ card.append(exitFullscreen);
22145
22152
  const frame = document.createElement("iframe");
22146
22153
  frame.className = "noodle-app-frame";
22147
22154
  frame.title = detail.title ?? detail.tool;
@@ -22155,8 +22162,31 @@ function mountAssistantApp(detail, actions, options = {}) {
22155
22162
  let mountedTransport;
22156
22163
  let destroyed = false;
22157
22164
  let initialized = false;
22158
- let hostContext = createAssistantAppHostContext(actions.theme);
22165
+ let currentTheme = actions.theme;
22166
+ let displayMode = "inline";
22167
+ let hostContext = createAssistantAppHostContext(
22168
+ currentTheme,
22169
+ options.allowFullscreen === true,
22170
+ displayMode
22171
+ );
22159
22172
  let destroyPromise;
22173
+ const applyDisplayMode = (requestedMode) => {
22174
+ displayMode = requestedMode === "fullscreen" && options.allowFullscreen === true ? "fullscreen" : "inline";
22175
+ card.toggleAttribute("data-fullscreen", displayMode === "fullscreen");
22176
+ exitFullscreen.hidden = displayMode !== "fullscreen";
22177
+ hostContext = createAssistantAppHostContext(
22178
+ currentTheme,
22179
+ options.allowFullscreen === true,
22180
+ displayMode
22181
+ );
22182
+ if (initialized) mountedBridge?.setHostContext(hostContext);
22183
+ return displayMode;
22184
+ };
22185
+ exitFullscreen.addEventListener("click", () => {
22186
+ if (destroyed) return;
22187
+ applyDisplayMode("inline");
22188
+ frame.focus();
22189
+ });
22160
22190
  const renderTimer = setTimeout(() => {
22161
22191
  if (initialized || !frame.isConnected) return;
22162
22192
  const note = document.createElement("div");
@@ -22214,6 +22244,11 @@ function mountAssistantApp(detail, actions, options = {}) {
22214
22244
  actions.updateModelContext(update);
22215
22245
  return {};
22216
22246
  };
22247
+ bridge.onrequestteardown = () => {
22248
+ if (destroyed) return;
22249
+ options.onTeardownRequest?.();
22250
+ void destroy();
22251
+ };
22217
22252
  bridge.onopenlink = async ({ url: url2 }) => {
22218
22253
  if (destroyed) return { isError: true };
22219
22254
  const current = options.getDetail?.() ?? detail;
@@ -22227,10 +22262,8 @@ function mountAssistantApp(detail, actions, options = {}) {
22227
22262
  return {};
22228
22263
  };
22229
22264
  bridge.onrequestdisplaymode = async ({ mode }) => {
22230
- const accepted = mode === "fullscreen" ? "fullscreen" : "inline";
22231
- if (destroyed) return { mode: accepted };
22232
- card.toggleAttribute("data-fullscreen", accepted === "fullscreen");
22233
- return { mode: accepted };
22265
+ if (destroyed) return { mode: displayMode };
22266
+ return { mode: applyDisplayMode(mode) };
22234
22267
  };
22235
22268
  bridge.oncalltool = async (params) => {
22236
22269
  if (destroyed) {
@@ -22275,7 +22308,12 @@ function mountAssistantApp(detail, actions, options = {}) {
22275
22308
  );
22276
22309
  const updateTheme = (theme) => {
22277
22310
  if (destroyed) return;
22278
- hostContext = createAssistantAppHostContext(theme);
22311
+ currentTheme = theme;
22312
+ hostContext = createAssistantAppHostContext(
22313
+ currentTheme,
22314
+ options.allowFullscreen === true,
22315
+ displayMode
22316
+ );
22279
22317
  if (initialized) mountedBridge?.setHostContext(hostContext);
22280
22318
  };
22281
22319
  const destroy = () => {
@@ -31599,6 +31637,33 @@ var AssistantElementPageContextController = class {
31599
31637
  }
31600
31638
  };
31601
31639
 
31640
+ // src/element-presentation-gate.ts
31641
+ var AssistantElementPresentationGate = class {
31642
+ #host;
31643
+ #primeSession;
31644
+ #focusComposer;
31645
+ constructor(host, primeSession, focusComposer) {
31646
+ this.#host = host;
31647
+ this.#primeSession = primeSession;
31648
+ this.#focusComposer = focusComposer;
31649
+ }
31650
+ syncOpenState() {
31651
+ const open = this.#host.hasAttribute("open");
31652
+ const presentationReady = this.#host.hasAttribute("data-presentation-ready");
31653
+ const launcher = this.#host.shadowRoot?.querySelector(".launcher");
31654
+ launcher?.setAttribute("aria-expanded", String(open && presentationReady));
31655
+ launcher?.setAttribute("aria-busy", String(open && !presentationReady));
31656
+ if (launcher) launcher.disabled = open && !presentationReady;
31657
+ if (open && this.#host.isConnected) this.#primeSession();
31658
+ }
31659
+ reveal() {
31660
+ if (this.#host.hasAttribute("data-presentation-ready")) return;
31661
+ this.#host.setAttribute("data-presentation-ready", "");
31662
+ this.syncOpenState();
31663
+ if (this.#host.hasAttribute("open")) queueMicrotask(this.#focusComposer);
31664
+ }
31665
+ };
31666
+
31602
31667
  // src/element-scroll-controller.ts
31603
31668
  var AssistantElementScrollController = class {
31604
31669
  #region;
@@ -31700,6 +31765,29 @@ var AssistantElementScrollController = class {
31700
31765
  }
31701
31766
  };
31702
31767
 
31768
+ // src/app-view-styles.ts
31769
+ var ASSISTANT_APP_VIEW_CSS = `
31770
+ .noodle-app-card { position: relative; display: grid; gap: 8px; width: 100%; margin: 4px 0 16px; overflow: hidden; color: var(--ns-assistant-app-text, inherit); border: 1px solid var(--ns-assistant-app-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider, var(--ns-app-view-border, #d1d5db)))); border-radius: var(--ns-assistant-card-radius, var(--ns-assistant-default-card-radius, 12px)); background: var(--ns-assistant-app, var(--ns-assistant-elevated, var(--ns-assistant-default-elevated, var(--ns-app-view-surface, #fff)))); }
31771
+ .noodle-app-title { padding: 10px 12px 0; font-size: .86em; font-weight: 650; }
31772
+ .noodle-app-fullscreen-exit { position: absolute; top: max(12px, env(safe-area-inset-top)); right: max(12px, env(safe-area-inset-right)); z-index: 1; display: grid; place-items: center; width: 44px; height: 44px; padding: 0; color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text, var(--ns-app-view-muted, #6b7280))); border: 1px solid var(--ns-assistant-secondary-button-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider, var(--ns-app-view-border, #d1d5db)))); border-radius: 999px; background: var(--ns-assistant-secondary-button, var(--ns-assistant-elevated, var(--ns-assistant-default-elevated, var(--ns-app-view-surface, #fff)))); box-shadow: 0 6px 20px rgba(0,0,0,.14); cursor: pointer; font: inherit; transition: color .15s ease, background .15s ease, transform .15s ease; }
31773
+ .noodle-app-fullscreen-exit[hidden] { display: none; }
31774
+ .noodle-app-fullscreen-exit::before { content: ''; width: 18px; height: 18px; background: currentColor; -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m6 6 12 12M18 6 6 18' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m6 6 12 12M18 6 6 18' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat; }
31775
+ .noodle-app-fullscreen-exit:hover { color: var(--ns-assistant-text, var(--ns-assistant-default-text, inherit)); background: var(--ns-assistant-hover, var(--ns-assistant-elevated, var(--ns-assistant-default-elevated, var(--ns-app-view-surface, #fff)))); }
31776
+ .noodle-app-fullscreen-exit:focus-visible { outline: 3px solid var(--ns-assistant-focus, var(--ns-assistant-default-focus, #2563eb)); outline-offset: 2px; }
31777
+ .noodle-app-fullscreen-exit:active { transform: scale(.96); }
31778
+ .noodle-app-frame { display: block; width: 100%; min-height: 120px; border: 0; background: transparent; }
31779
+ .noodle-app-fallback { padding: 14px 16px; font-size: 13px; color: var(--ns-assistant-muted, var(--ns-assistant-default-muted, var(--ns-app-view-muted, #6b7280))); }
31780
+ .noodle-app-card[data-fullscreen] { position: fixed; inset: 0; z-index: 2; margin: 0; border-radius: 0; background: var(--ns-assistant-panel, var(--ns-assistant-default-panel, var(--ns-app-view-surface, #fff))); }
31781
+ .noodle-app-card[data-fullscreen] .noodle-app-frame { height: 100% !important; }
31782
+ `;
31783
+ var ASSISTANT_APP_VIEW_STYLES = `<style>
31784
+ *, *::before, *::after { box-sizing: border-box; }
31785
+ :host { --ns-app-view-surface: #fff; --ns-app-view-border: #d1d5db; --ns-app-view-muted: #6b7280; display: block; min-width: 0; color: #111827; color-scheme: light; font: inherit; }
31786
+ :host([data-theme="dark"]) { --ns-app-view-surface: #17191f; --ns-app-view-border: #343741; --ns-app-view-muted: #a7adb8; color: #f3f4f6; color-scheme: dark; }
31787
+ :host([hidden]) { display: none; }
31788
+ ${ASSISTANT_APP_VIEW_CSS}
31789
+ </style>`;
31790
+
31703
31791
  // src/element-styles.ts
31704
31792
  var ASSISTANT_ELEMENT_STYLES = `<style>
31705
31793
  *, *::before, *::after { box-sizing: border-box; }
@@ -31714,9 +31802,10 @@ var ASSISTANT_ELEMENT_STYLES = `<style>
31714
31802
  .launcher:hover { transform: translateY(-2px) scale(1.03); box-shadow: 0 8px 26px rgba(0,0,0,.16); }
31715
31803
  .launcher img { width: 28px; height: 28px; border-radius: 50%; object-fit: contain; }
31716
31804
  .panel { display: none; flex-direction: column; width: min(var(--ns-assistant-panel-width, var(--ns-assistant-default-panel-width)), calc(100vw - 40px)); min-height: var(--ns-assistant-min-height, var(--ns-assistant-default-min-height)); max-height: min(var(--ns-assistant-max-height, var(--ns-assistant-default-max-height)), calc(100vh - 40px)); overflow: hidden; color: var(--ns-assistant-panel-text, var(--ns-assistant-text, var(--ns-assistant-default-text))); background: var(--ns-assistant-panel, var(--ns-assistant-default-panel)); border: 1px solid var(--ns-assistant-panel-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))); border-radius: var(--ns-assistant-panel-radius, var(--ns-assistant-default-panel-radius)); box-shadow: var(--ns-assistant-shadow, var(--ns-assistant-default-shadow)); transform-origin: bottom right; isolation: isolate; animation: halo-panel-in .28s cubic-bezier(.23,1,.32,1) both; }
31717
- :host([open]) .panel { display: flex; }
31805
+ :host([open][data-presentation-ready]) .panel { display: flex; }
31718
31806
  .panel { position: relative; }
31719
- :host([open]) .launcher { display: none; }
31807
+ :host([open][data-presentation-ready]) .launcher { display: none; }
31808
+ :host([open]:not([data-presentation-ready])) .launcher { cursor: progress; opacity: .72; transform: none; }
31720
31809
  header { display: flex; flex: 0 0 auto; gap: 10px; align-items: center; min-height: 64px; padding: 12px 18px; color: var(--ns-assistant-header-text, inherit); background: var(--ns-assistant-header, transparent); border-bottom: 1px solid color-mix(in srgb, var(--ns-assistant-header-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))) 78%, transparent); }
31721
31810
  header strong { flex: 1; font-size: 16px; font-weight: 600; letter-spacing: 0; }
31722
31811
  .brand-logo { display: block; max-width: 120px; max-height: 28px; object-fit: contain; }
@@ -31801,12 +31890,7 @@ var ASSISTANT_ELEMENT_STYLES = `<style>
31801
31890
  .input-request input[type="checkbox"] { width: auto; min-height: auto; justify-self: start; accent-color: var(--ns-assistant-accent, var(--ns-assistant-default-accent)); }
31802
31891
  .input-request input:focus-visible, .input-request select:focus-visible { border-color: var(--ns-assistant-focus, var(--ns-assistant-default-focus)); box-shadow: 0 0 0 2px var(--ns-assistant-focus, var(--ns-assistant-default-focus)); }
31803
31892
  .input-request .proposal-actions { margin-top: 2px; }
31804
- .noodle-app-card { position: relative; display: grid; gap: 8px; width: 100%; margin: 4px 0 16px; overflow: hidden; color: var(--ns-assistant-app-text, inherit); border: 1px solid var(--ns-assistant-app-border, var(--ns-assistant-divider, var(--ns-assistant-default-divider))); border-radius: var(--ns-assistant-card-radius, var(--ns-assistant-default-card-radius)); background: var(--ns-assistant-app, var(--ns-assistant-elevated, var(--ns-assistant-default-elevated))); }
31805
- .noodle-app-title { padding: 10px 12px 0; font-size: .86em; font-weight: 650; }
31806
- .noodle-app-frame { display: block; width: 100%; min-height: 120px; border: 0; background: transparent; }
31807
- .noodle-app-fallback { padding: 14px 16px; font-size: 13px; color: var(--ns-assistant-muted, var(--ns-assistant-default-muted, #6b7280)); }
31808
- .noodle-app-card[data-fullscreen] { position: fixed; inset: 0; z-index: 2; margin: 0; border-radius: 0; background: var(--ns-assistant-panel, var(--ns-assistant-default-panel)); }
31809
- .noodle-app-card[data-fullscreen] .noodle-app-frame { height: 100% !important; }
31893
+ ${ASSISTANT_APP_VIEW_CSS}
31810
31894
  form { display: flex; flex: 0 0 auto; gap: 8px; align-items: center; min-height: 56px; margin: 8px 16px 16px; padding: 6px 6px 6px 16px; color: var(--ns-assistant-composer-text, inherit); border: 1px solid var(--ns-assistant-composer-border, color-mix(in srgb, var(--ns-assistant-divider, var(--ns-assistant-default-divider)) 86%, transparent)); border-radius: var(--ns-assistant-input-radius, var(--ns-assistant-default-input-radius)); background: var(--ns-assistant-composer, color-mix(in srgb, var(--ns-assistant-input, var(--ns-assistant-default-input)) 92%, transparent)); box-shadow: 0 1px 2px rgba(0,0,0,.04), 0 8px 24px rgba(0,0,0,.035); transition: border-color .15s ease, box-shadow .15s ease; }
31811
31895
  form:focus-within { box-shadow: inset 0 0 0 1px var(--ns-assistant-focus, var(--ns-assistant-default-focus)), 0 0 0 2px var(--ns-assistant-focus, var(--ns-assistant-default-focus)); }
31812
31896
  textarea { flex: 1; min-height: 24px; max-height: 120px; resize: none; padding: 0; color: var(--ns-assistant-text, var(--ns-assistant-default-text)); background: transparent; border: 0; border-radius: 0; outline: 0; box-shadow: none; appearance: none; -webkit-appearance: none; font: inherit; font-size: 16px; line-height: 24px; }
@@ -32342,6 +32426,11 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32342
32426
  #automaticPageContext = new AssistantElementPageContextController(
32343
32427
  () => !this.#pageContext
32344
32428
  );
32429
+ #presentationGate = new AssistantElementPresentationGate(
32430
+ this,
32431
+ () => this.#primeSession(),
32432
+ () => this.focusComposer()
32433
+ );
32345
32434
  #pendingUserEcho;
32346
32435
  #sessionState = "idle";
32347
32436
  #startOpenApplied = false;
@@ -32412,6 +32501,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32412
32501
  if (!this.sessionEndpoint) this.sessionEndpoint = this.getAttribute("session-endpoint") ?? "";
32413
32502
  if (!this.embedId) this.embedId = this.getAttribute("embed-id") ?? "";
32414
32503
  if (!this.serviceUrl) this.serviceUrl = this.getAttribute("service-url") ?? "";
32504
+ if (!this.sessionEndpoint && !this.embedId) this.#presentationGate.reveal();
32415
32505
  this.#media = globalThis.matchMedia?.("(prefers-color-scheme: dark)");
32416
32506
  this.#media?.addEventListener("change", this.#handleSystemTheme);
32417
32507
  document.addEventListener("keydown", this.#handleDocumentKeydown);
@@ -32428,7 +32518,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32428
32518
  } else if (!this.embedId) {
32429
32519
  this.#primeSession();
32430
32520
  }
32431
- this.#syncOpenState();
32521
+ this.#presentationGate.syncOpenState();
32432
32522
  }
32433
32523
  disconnectedCallback() {
32434
32524
  this.#conversationGeneration += 1;
@@ -32446,7 +32536,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32446
32536
  }
32447
32537
  attributeChangedCallback(name21) {
32448
32538
  if (name21 === "theme") this.#applyTheme();
32449
- if (name21 === "open") this.#syncOpenState();
32539
+ if (name21 === "open") this.#presentationGate.syncOpenState();
32450
32540
  }
32451
32541
  open() {
32452
32542
  this.setAttribute("open", "");
@@ -32636,6 +32726,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32636
32726
  const bootstrap = client.connect().catch((error51) => {
32637
32727
  if (generation !== this.#conversationGeneration || this.#client !== client) return;
32638
32728
  this.#dispatchClientError(error51, "session_failed");
32729
+ this.#presentationGate.reveal();
32639
32730
  }).finally(() => {
32640
32731
  if (this.#sessionBootstrap === bootstrap) this.#sessionBootstrap = void 0;
32641
32732
  });
@@ -32645,6 +32736,7 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32645
32736
  this.dispatchEvent(new CustomEvent("assistant-event", { detail: event }));
32646
32737
  this.#events.handle(event);
32647
32738
  if (event.event === "session_started") {
32739
+ this.#presentationGate.reveal();
32648
32740
  this.#setSessionState("ready");
32649
32741
  this.#messages?.querySelector(".conversation-error")?.remove();
32650
32742
  }
@@ -32814,11 +32906,6 @@ var NoodleAssistantElement = class extends HTMLElementBase {
32814
32906
  this.#sessionState = state;
32815
32907
  syncAssistantSessionVisualState(this, this.shadowRoot, state, this.#appearance);
32816
32908
  }
32817
- #syncOpenState() {
32818
- const open = this.hasAttribute("open");
32819
- this.shadowRoot?.querySelector(".launcher")?.setAttribute("aria-expanded", String(open));
32820
- if (open && this.isConnected) this.#primeSession();
32821
- }
32822
32909
  #closeAndRestoreFocus() {
32823
32910
  this.close();
32824
32911
  this.shadowRoot?.querySelector(".launcher")?.focus();
@@ -32963,52 +33050,208 @@ function registerNoodleAssistant() {
32963
33050
 
32964
33051
  // src/react-app-view.ts
32965
33052
  var import_react = require("react");
32966
- function NoodleAppView({
32967
- client,
32968
- view,
32969
- theme = "light",
32970
- className,
32971
- onError
32972
- }) {
32973
- const container = (0, import_react.useRef)(null);
32974
- const mounted = (0, import_react.useRef)(null);
32975
- const current = (0, import_react.useRef)({ view, theme, onError });
32976
- const viewId = view.id;
32977
- const resourceUri = view.resourceUri;
32978
- (0, import_react.useEffect)(() => {
32979
- current.current = { view, theme, onError };
32980
- });
32981
- (0, import_react.useEffect)(() => {
32982
- const host = container.current;
32983
- if (!host) return;
32984
- const initialView = { ...current.current.view, id: viewId, resourceUri };
33053
+
33054
+ // src/app-view-element.ts
33055
+ var APP_VIEW_TAG_NAME = "noodle-app-view";
33056
+ var HTMLElementBase2 = globalThis.HTMLElement ?? class {
33057
+ };
33058
+ function viewIdentity(view) {
33059
+ return `${view.id}\0${view.resourceUri}`;
33060
+ }
33061
+ var NoodleAppViewElement = class extends HTMLElementBase2 {
33062
+ static observedAttributes = ["allow-fullscreen", "theme"];
33063
+ #allowFullscreen = false;
33064
+ #client;
33065
+ #view;
33066
+ #theme = "light";
33067
+ #container;
33068
+ #mounted;
33069
+ #mountedClient;
33070
+ #mountedAllowFullscreen = false;
33071
+ #mountedIdentity;
33072
+ #requestedTeardown;
33073
+ get client() {
33074
+ return this.#client;
33075
+ }
33076
+ /** Allow the hosted App to request fullscreen. Disabled by default. */
33077
+ get allowFullscreen() {
33078
+ return this.#allowFullscreen;
33079
+ }
33080
+ set allowFullscreen(value) {
33081
+ const next = value === true;
33082
+ if (next === this.#allowFullscreen) return;
33083
+ this.#allowFullscreen = next;
33084
+ this.toggleAttribute("allow-fullscreen", next);
33085
+ this.#reconcile();
33086
+ }
33087
+ set client(value) {
33088
+ if (value === this.#client) return;
33089
+ this.#client = value;
33090
+ this.#reconcile();
33091
+ }
33092
+ get view() {
33093
+ return this.#view;
33094
+ }
33095
+ set view(value) {
33096
+ this.#view = value;
33097
+ this.#reconcile();
33098
+ }
33099
+ get theme() {
33100
+ return this.#theme;
33101
+ }
33102
+ set theme(value) {
33103
+ const next = value === "dark" ? "dark" : "light";
33104
+ const changed = next !== this.#theme;
33105
+ this.#theme = next;
33106
+ if (this.getAttribute("data-theme") !== next) this.setAttribute("data-theme", next);
33107
+ if (changed && this.#mounted) updateAssistantAppTheme(this.#mounted, next);
33108
+ }
33109
+ connectedCallback() {
33110
+ this.#ensureContainer();
33111
+ this.#upgradeProperty("allowFullscreen");
33112
+ this.#upgradeProperty("client");
33113
+ this.#upgradeProperty("theme");
33114
+ this.#upgradeProperty("view");
33115
+ this.#reconcile();
33116
+ }
33117
+ disconnectedCallback() {
33118
+ this.#requestedTeardown = void 0;
33119
+ this.#releaseMount();
33120
+ }
33121
+ attributeChangedCallback(name21, _previous, value) {
33122
+ if (name21 === "allow-fullscreen") this.allowFullscreen = value !== null;
33123
+ if (name21 === "theme") this.theme = value === "dark" ? "dark" : "light";
33124
+ }
33125
+ #ensureContainer() {
33126
+ if (this.#container) return this.#container;
33127
+ const root = this.shadowRoot ?? this.attachShadow({ mode: "open" });
33128
+ root.innerHTML = ASSISTANT_APP_VIEW_STYLES;
33129
+ const container = document.createElement("div");
33130
+ container.className = "noodle-app-mount";
33131
+ container.setAttribute("part", "mount");
33132
+ root.append(container);
33133
+ this.#container = container;
33134
+ return container;
33135
+ }
33136
+ #upgradeProperty(name21) {
33137
+ const property = Object.getOwnPropertyDescriptor(this, name21);
33138
+ if (!property) return;
33139
+ Reflect.deleteProperty(this, name21);
33140
+ Reflect.set(this, name21, property.value);
33141
+ }
33142
+ #releaseMount() {
33143
+ const mounted = this.#mounted;
33144
+ this.#mounted = void 0;
33145
+ this.#mountedClient = void 0;
33146
+ this.#mountedAllowFullscreen = false;
33147
+ this.#mountedIdentity = void 0;
33148
+ this.#container?.replaceChildren();
33149
+ if (mounted) void unmountAssistantApp(mounted);
33150
+ }
33151
+ #reconcile() {
33152
+ if (!this.isConnected) return;
33153
+ const client = this.#client;
33154
+ const detail = this.#view;
33155
+ const identity = detail ? viewIdentity(detail) : void 0;
33156
+ if (this.#mounted) {
33157
+ if (client === this.#mountedClient && identity === this.#mountedIdentity && this.#allowFullscreen === this.#mountedAllowFullscreen) {
33158
+ return;
33159
+ }
33160
+ this.#releaseMount();
33161
+ }
33162
+ if (this.#requestedTeardown && (client !== this.#requestedTeardown.client || identity !== this.#requestedTeardown.identity)) {
33163
+ this.#requestedTeardown = void 0;
33164
+ }
33165
+ if (!client || !detail?.html || !identity || this.#requestedTeardown) return;
33166
+ const initialHtml = detail.html;
32985
33167
  const sandboxUrl = client.appSandboxUrl?.();
32986
- const card = mountAssistantApp(
32987
- initialView,
33168
+ let card;
33169
+ card = mountAssistantApp(
33170
+ detail,
32988
33171
  {
32989
33172
  client,
32990
33173
  sendMessage: (message) => client.sendMessage(message),
32991
33174
  updateModelContext: (update) => client.updateModelContext(update),
32992
- theme: current.current.theme
33175
+ theme: this.#theme
32993
33176
  },
32994
33177
  {
33178
+ allowFullscreen: this.#allowFullscreen,
32995
33179
  ...sandboxUrl ? { sandboxUrl } : {},
32996
- getDetail: () => current.current.view,
32997
- onRenderFailure: (failure) => current.current.onError?.(failure)
33180
+ getDetail: () => {
33181
+ const latest = this.#view;
33182
+ if (this.#client !== client || !latest || viewIdentity(latest) !== identity)
33183
+ return detail;
33184
+ return latest.html ? latest : { ...latest, html: initialHtml };
33185
+ },
33186
+ onRenderFailure: (failure) => {
33187
+ this.dispatchEvent(
33188
+ new CustomEvent("assistant-error", {
33189
+ detail: failure,
33190
+ bubbles: true,
33191
+ composed: true
33192
+ })
33193
+ );
33194
+ },
33195
+ onTeardownRequest: () => {
33196
+ if (this.#mounted !== card) return;
33197
+ this.#mounted = void 0;
33198
+ this.#mountedClient = void 0;
33199
+ this.#mountedIdentity = void 0;
33200
+ this.#requestedTeardown = { client, identity };
33201
+ }
32998
33202
  }
32999
33203
  );
33000
33204
  if (!card) return;
33001
- mounted.current = card;
33002
- host.replaceChildren(card);
33205
+ this.#mounted = card;
33206
+ this.#mountedClient = client;
33207
+ this.#mountedAllowFullscreen = this.#allowFullscreen;
33208
+ this.#mountedIdentity = identity;
33209
+ this.#ensureContainer().replaceChildren(card);
33210
+ }
33211
+ };
33212
+
33213
+ // src/app-view.ts
33214
+ function registerNoodleAppView() {
33215
+ if (!globalThis.customElements?.get(APP_VIEW_TAG_NAME)) {
33216
+ globalThis.customElements?.define(APP_VIEW_TAG_NAME, NoodleAppViewElement);
33217
+ }
33218
+ }
33219
+ registerNoodleAppView();
33220
+
33221
+ // src/react-app-view.ts
33222
+ function NoodleAppView({
33223
+ client,
33224
+ view,
33225
+ theme = "light",
33226
+ allowFullscreen = false,
33227
+ className,
33228
+ onError
33229
+ }) {
33230
+ const elementRef = (0, import_react.useRef)(null);
33231
+ const onErrorRef = (0, import_react.useRef)(onError);
33232
+ onErrorRef.current = onError;
33233
+ (0, import_react.useEffect)(() => {
33234
+ const element = elementRef.current;
33235
+ if (!element) return;
33236
+ const handleError = (event) => {
33237
+ onErrorRef.current?.(event.detail);
33238
+ };
33239
+ element.addEventListener("assistant-error", handleError);
33240
+ registerNoodleAppView();
33003
33241
  return () => {
33004
- if (mounted.current === card) mounted.current = null;
33005
- void unmountAssistantApp(card);
33242
+ element.removeEventListener("assistant-error", handleError);
33006
33243
  };
33007
- }, [client, resourceUri, viewId]);
33244
+ }, []);
33008
33245
  (0, import_react.useEffect)(() => {
33009
- if (mounted.current) updateAssistantAppTheme(mounted.current, theme);
33010
- }, [theme]);
33011
- return (0, import_react.createElement)("div", { ref: container, className });
33246
+ const element = elementRef.current;
33247
+ if (!element) return;
33248
+ if (element.client !== client) element.view = void 0;
33249
+ element.allowFullscreen = allowFullscreen;
33250
+ element.client = client;
33251
+ element.theme = theme;
33252
+ element.view = view;
33253
+ }, [allowFullscreen, client, theme, view]);
33254
+ return (0, import_react.createElement)(APP_VIEW_TAG_NAME, { ref: elementRef, class: className });
33012
33255
  }
33013
33256
 
33014
33257
  // src/react.ts