@salsita-ai/sdk 0.50.0-develop.98e8b1502f → 0.50.0-develop.a926ccd94f

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 (83) hide show
  1. package/dist/application-spec/generate-application/code/templates/skills/phoenix-feedback-skill.js +1 -1
  2. package/dist/application-spec/generate-application/code/templates/skills/phoenix-skill.js +1 -1
  3. package/dist/configurator/application-runtime/extensions/systems/layout-system/layout-system.d.ts +2 -0
  4. package/dist/configurator/application-runtime/extensions/systems/layout-system/layout-system.js +1 -1
  5. package/dist/configurator/assistant/use-assistant-chat.d.ts +15 -5
  6. package/dist/configurator/assistant/use-assistant-chat.js +1 -1
  7. package/dist/configurator/common/frontend-analytics.d.ts +21 -21
  8. package/dist/configurator/scene/scene-analytics/scene-analytics.d.ts +2 -2
  9. package/dist/configurator/ui/assistant/AssistantChips.d.ts +11 -0
  10. package/dist/configurator/ui/assistant/AssistantChips.js +1 -0
  11. package/dist/configurator/ui/assistant/AssistantComposer.d.ts +11 -0
  12. package/dist/configurator/ui/assistant/AssistantComposer.js +1 -0
  13. package/dist/configurator/ui/assistant/AssistantMarkdown.d.ts +7 -0
  14. package/dist/configurator/ui/assistant/AssistantMarkdown.js +1 -0
  15. package/dist/configurator/ui/assistant/AssistantPanel.d.ts +11 -0
  16. package/dist/configurator/ui/assistant/AssistantPanel.js +1 -0
  17. package/dist/configurator/ui/assistant/AssistantPanelProvider.d.ts +42 -0
  18. package/dist/configurator/ui/assistant/AssistantPanelProvider.js +1 -0
  19. package/dist/configurator/ui/assistant/AssistantThread.d.ts +11 -0
  20. package/dist/configurator/ui/assistant/AssistantThread.js +1 -0
  21. package/dist/configurator/ui/assistant/assistant-failure.d.ts +18 -0
  22. package/dist/configurator/ui/assistant/assistant-failure.js +1 -0
  23. package/dist/configurator/ui/assistant/index.d.ts +2 -0
  24. package/dist/configurator/ui/assistant/index.js +1 -0
  25. package/dist/configurator/ui/assistant/streamed-markdown.d.ts +6 -0
  26. package/dist/configurator/ui/assistant/streamed-markdown.js +2 -0
  27. package/dist/configurator/ui/assistant/theme.d.ts +2 -0
  28. package/dist/configurator/ui/assistant/theme.js +1 -0
  29. package/dist/configurator/ui/assistant/thread-items.d.ts +37 -0
  30. package/dist/configurator/ui/assistant/thread-items.js +1 -0
  31. package/dist/configurator/ui/assistant/tool-chip.d.ts +37 -0
  32. package/dist/configurator/ui/assistant/tool-chip.js +1 -0
  33. package/dist/configurator/ui/assistant/use-follow-stream.d.ts +17 -0
  34. package/dist/configurator/ui/assistant/use-follow-stream.js +1 -0
  35. package/dist/configurator/ui/configurator-layout/universal/DesktopLayout.js +5 -5
  36. package/dist/configurator/ui/configurator-layout/universal/Layout.js +1 -1
  37. package/dist/configurator/ui/configurator-layout/universal/MobileLayout.d.ts +2 -0
  38. package/dist/configurator/ui/configurator-layout/universal/MobileLayout.js +3 -3
  39. package/dist/configurator/ui/legacy/mobile-controls/MobileControls.d.ts +1 -1
  40. package/dist/configurator/ui/phoenix-layout-elements/AssistantToolbarButton.d.ts +10 -0
  41. package/dist/configurator/ui/phoenix-layout-elements/AssistantToolbarButton.js +1 -0
  42. package/dist/configurator/ui/phoenix-layout-elements/index.d.ts +1 -0
  43. package/dist/configurator/ui/phoenix-layout-elements/index.js +1 -1
  44. package/dist/docs/index.json +1 -1
  45. package/dist/docs/layouts/toolbar.mdx +4 -0
  46. package/dist/docs/phoenix/phoenix-cli.mdx +1 -1
  47. package/dist/domain/domain-context/index.d.ts +1 -1
  48. package/dist/domain/system-components/phoenix-layout-elements/assistant-toolbar-button.d.ts +44 -0
  49. package/dist/domain/system-components/phoenix-layout-elements/assistant-toolbar-button.js +1 -0
  50. package/dist/domain/system-components/phoenix-layout-elements/index.d.ts +1 -0
  51. package/dist/domain/system-components/phoenix-layout-elements/index.js +1 -1
  52. package/dist/domain/system-components/phoenix-layout-elements/phoenix-layout-element.d.ts +2 -1
  53. package/dist/domain/system-components/phoenix-layout-elements/phoenix-layout-element.js +1 -1
  54. package/dist/i18n/locales/cs/messages.js +1 -1
  55. package/dist/i18n/locales/da/messages.js +1 -1
  56. package/dist/i18n/locales/de/messages.js +1 -1
  57. package/dist/i18n/locales/de-AT/messages.js +1 -1
  58. package/dist/i18n/locales/en/messages.js +1 -1
  59. package/dist/i18n/locales/en-AU/messages.js +1 -1
  60. package/dist/i18n/locales/en-GB/messages.js +1 -1
  61. package/dist/i18n/locales/es/messages.js +1 -1
  62. package/dist/i18n/locales/fr/messages.js +1 -1
  63. package/dist/i18n/locales/hu/messages.js +1 -1
  64. package/dist/i18n/locales/it/messages.js +1 -1
  65. package/dist/i18n/locales/nl/messages.js +1 -1
  66. package/dist/i18n/locales/pl/messages.js +1 -1
  67. package/dist/i18n/locales/pt/messages.js +1 -1
  68. package/dist/i18n/locales/ro/messages.js +1 -1
  69. package/dist/i18n/locales/sk/messages.js +1 -1
  70. package/dist/i18n/locales/tr/messages.js +1 -1
  71. package/dist/legacy/domain-spec/__generated/domain-schema.d.ts +6 -0
  72. package/dist/phoenix/commands/deploy.js +2 -2
  73. package/dist/react-sdk/application-runtime/common-services/analytics/analytics-service.d.ts +2 -2
  74. package/dist/react-sdk/application-runtime/common-services/analytics/domain-controls-analytics.d.ts +21 -21
  75. package/dist/react-sdk/application-runtime/hooks/data/use-published-data.d.ts +1 -1
  76. package/dist/ui-sdk/components/mobile-controls/index.d.ts +1 -1
  77. package/dist/ui-sdk/theme/types.d.ts +4 -0
  78. package/package.json +4 -7
  79. package/schema/domain-spec.json +24 -0
  80. package/dist/extras/assistant/AssistantChat.d.ts +0 -6
  81. package/dist/extras/assistant/AssistantChat.js +0 -1
  82. package/dist/extras/assistant/index.d.ts +0 -1
  83. package/dist/extras/assistant/index.js +0 -1
@@ -51,4 +51,4 @@ The message leaves this project and is read by the Phoenix team. Never include:
51
51
  - secrets and credentials: API tokens, keys, passwords or \`.env\` values, also when they appear inside an error or a log you quote. Replace each with \`<redacted>\`;
52
52
  - personal data, or data of the project's customers and end users, such as names, email addresses and orders, other than a contact the user asked you to add.
53
53
 
54
- Parts of this project may go into a message: file names and paths, snippets of its specs and code, and the user's own words. They are sent only as part of a draft the user saw and approved, which is why the draft you show is always the full text.`;export const generatePhoenixFeedbackSkill=()=>e({name:"phoenix-feedback",description:"Drafting feedback about the Phoenix SDK, CLI, documentation or Studio for the Phoenix team and sending it with the phoenix feedback command once the user approves the draft, covering when to raise it, the report template with the build and configurator links, the evaluation interview at the end of a run, how to send it and what must never be included. Use when the SDK or CLI misbehaves, a doc or skill misled you, a missing feature forced a workaround, the user asks to send feedback, or the user is done with the configurator and the run should be evaluated.",sections:[t,o,"## Report Template\nWrite a report as plain text with the labels below, in this order, so the Phoenix team reads every report the same way. Keep every label; write `none` or `not deployed yet` where you have nothing to put.\n```text\nBuild: <build id> <Studio build page link>\nConfigurator: <live configurator link>\n\nWhat I was doing:\n<the task and the step you were on>\n\nWhat happened and what I expected:\n<what you ran or called, the result you got, and the result you expected>\n\nExact error or agent output:\n<the error text or the output, copied unchanged>\n\nWorkaround:\n<what you did instead, or none>\n\nConversation log:\n<the conversation condensed to the turns that matter: what the user asked, what you tried, what failed>\n```\n- **Build** is the latest build of this project, the one whose sources show the problem. `pnpm phoenix deploy` prints its id as `Build <id> started` and its page as `Follow the build in Studio: <url>`. When that line is missing, compose the link as `https://<workspace>.<studio host>/builder/deployments/<build id>`: the workspace is the one named at `phoenix login`, and the host is `studio.eu.salsita.ai` for the `eu` region, `studio.salsita.ai` for `us` and `studio-staging.salsita.app` for staging. A Studio address passed to login with `--studioUrl` replaces the workspace and host. Never invent a build id: when the project was not deployed, write `not deployed yet`.\n- **Configurator** is the live address `pnpm phoenix deploy` prints once the build is live.\n- The team downloads the sources of the build from its Studio page, so do not paste whole files; quote only the lines that show the problem.\n- Quote errors and output exactly. A report that paraphrases an error cannot be searched for.\n- Condense the conversation log, do not dump it: keep the user's requests, your decisions and the failures, and leave out tool output that the other sections already carry.",n,"## How to Send It\nWrite a report or an evaluation to a text file and send the file, so the shell changes nothing in it:\n```bash\npnpm phoenix feedback --file <path> --type bug --agent '<your agent name>'\n```\nPut the file outside the project, in the system's temporary folder, and delete it after sending. A note of a line or two can go in as an argument instead:\n```bash\npnpm phoenix feedback '<message>' --type idea --agent '<your agent name>'\n```\n- Give either `--file` or the message argument, never both.\n- Put a message argument in single quotes, so the shell passes backticks, `$` and double quotes through unchanged. In bash or zsh, write a single quote inside the message as `'\\''`; in PowerShell, double it instead.\n- `--type` is `bug`, `idea` or `other` (default `other`). Use `idea` for a missing feature or an improvement, and `other` for an evaluation.\n- `--agent` names you, the coding agent, so the team can tell agent feedback from a person's.\n- `--contact '<email>'` only when the user wants a reply and gave you the contact to use.\n- The workspace, user, SDK version and the spec's `project` are attached automatically. `--projectSlug <slug>` names another project of the workspace.\n- A message holds at most 100,000 characters. Shorten the conversation log first when a report is longer.\n\nThe command needs `phoenix login`. If it answers that you are not signed in, tell the user once and continue; do not sign in yourself, because the sign-in is approved in a browser.\n\nAfter sending, confirm it to the user in one line.",a]});
54
+ Parts of this project may go into a message: file names and paths, snippets of its specs and code, and the user's own words. They are sent only as part of a draft the user saw and approved, which is why the draft you show is always the full text.`;export const generatePhoenixFeedbackSkill=()=>e({name:"phoenix-feedback",description:"Evaluating a finished run and drafting feedback about the Phoenix SDK, CLI, documentation or Studio for the Phoenix team, sent with the phoenix feedback command once the user approves the draft. Use right after phoenix deploy reports the configurator live or when the user says they are done, to offer the evaluation of the run, and when the SDK or CLI misbehaves, a doc or skill misled you, a missing feature forced a workaround or the user asks to send feedback.",sections:[t,o,"## Report Template\nWrite a report as plain text with the labels below, in this order, so the Phoenix team reads every report the same way. Keep every label; write `none` or `not deployed yet` where you have nothing to put.\n```text\nBuild: <build id> <Studio build page link>\nConfigurator: <live configurator link>\n\nWhat I was doing:\n<the task and the step you were on>\n\nWhat happened and what I expected:\n<what you ran or called, the result you got, and the result you expected>\n\nExact error or agent output:\n<the error text or the output, copied unchanged>\n\nWorkaround:\n<what you did instead, or none>\n\nConversation log:\n<the conversation condensed to the turns that matter: what the user asked, what you tried, what failed>\n```\n- **Build** is the latest build of this project, the one whose sources show the problem. `pnpm phoenix deploy` prints its id as `Build <id> started` and its page as `Follow the build in Studio: <url>`. When that line is missing, compose the link as `https://<workspace>.<studio host>/builder/deployments/<build id>`: the workspace is the one named at `phoenix login`, and the host is `studio.eu.salsita.ai` for the `eu` region, `studio.salsita.ai` for `us` and `studio-staging.salsita.app` for staging. A Studio address passed to login with `--studioUrl` replaces the workspace and host. Never invent a build id: when the project was not deployed, write `not deployed yet`.\n- **Configurator** is the live address `pnpm phoenix deploy` prints once the build is live.\n- The team downloads the sources of the build from its Studio page, so do not paste whole files; quote only the lines that show the problem.\n- Quote errors and output exactly. A report that paraphrases an error cannot be searched for.\n- Condense the conversation log, do not dump it: keep the user's requests, your decisions and the failures, and leave out tool output that the other sections already carry.",n,"## How to Send It\nWrite a report or an evaluation to a text file and send the file, so the shell changes nothing in it:\n```bash\npnpm phoenix feedback --file <path> --type bug --agent '<your agent name>'\n```\nPut the file outside the project, in the system's temporary folder, and delete it after sending. A note of a line or two can go in as an argument instead:\n```bash\npnpm phoenix feedback '<message>' --type idea --agent '<your agent name>'\n```\n- Give either `--file` or the message argument, never both.\n- Put a message argument in single quotes, so the shell passes backticks, `$` and double quotes through unchanged. In bash or zsh, write a single quote inside the message as `'\\''`; in PowerShell, double it instead.\n- `--type` is `bug`, `idea` or `other` (default `other`). Use `idea` for a missing feature or an improvement, and `other` for an evaluation.\n- `--agent` names you, the coding agent, so the team can tell agent feedback from a person's.\n- `--contact '<email>'` only when the user wants a reply and gave you the contact to use.\n- The workspace, user, SDK version and the spec's `project` are attached automatically. `--projectSlug <slug>` names another project of the workspace.\n- A message holds at most 100,000 characters. Shorten the conversation log first when a report is longer.\n\nThe command needs `phoenix login`. If it answers that you are not signed in, tell the user once and continue; do not sign in yourself, because the sign-in is approved in a browser.\n\nAfter sending, confirm it to the user in one line.",a]});
@@ -1 +1 @@
1
- import{renderAgentSkill as e}from"./agent-skill.js";import{sdkGuidanceSections as t}from"./sdk-content.js";export const followsCanaryBuilds=e=>!e.useWorkspace&&e.sdkVersion.includes("-develop.");export const generatePhoenixSkill=({checkLatestCanary:n})=>e({name:"phoenix",description:`Phoenix SDK project conventions, covering project structure, architecture principles, key patterns, file and export rules, webhook membership, versioning the domain model once it is deployed, showing the user the dev preview and the build of a deploy, and how to search the SDK documentation${n?", and the internal-beta check for the latest canary SDK build":""}. Use before writing or changing specs or source code in this Phoenix configurator project.`,sections:[...n?["## Stay on the Latest Canary\nDuring the internal beta this project uses the `canary` build of the SDK, which is published from every change to the platform. Once per conversation, before you change anything, compare the installed version with the newest canary:\n```bash\npnpm list @salsita-ai/sdk --depth 0\nnpm view @salsita-ai/sdk@canary version\n```\nWhen they differ, tell the user which version you are moving to, then update the project and regenerate its files:\n```bash\npnpm add @salsita-ai/sdk@canary\npnpm phoenix sync\n```\n`sync` rewrites the Phoenix skills, so read the ones you use again after it. When the user runs a globally installed `phoenix`, update that too with `npm install -g @salsita-ai/sdk@canary`. If the project no longer builds after the update, stop and tell the user; the `phoenix-feedback` skill covers reporting it."]:[],t.projectStructure,t.architecturePrinciples,t.keyPatterns,t.fileConventions,t.exportRules,t.webhookMembership,t.doNotEdit,t.modelVersioning,"## Show the Preview While You Work\nThe user should see the configurator change as you build it. Before your first change in a conversation, make sure `pnpm phoenix dev` is running (start it in the background when it is not) and open the address it prints as `Preview: <url>` (also written to `.phoenix/phoenix-dev.log`) in the user's browser: `open '<url>'` on macOS, `xdg-open '<url>'` on Linux, `Start-Process '<url>'` in PowerShell, `start \"\" \"<url>\"` in cmd. Keep the quotes, the shell would otherwise read a `?` in the address as a pattern. The log keeps the lines of earlier runs, so when you have just started dev, wait for the `Preview:` line written after your start and use the last one. Open it once; the preview reloads on every change, so tell the user to keep that tab open instead of opening it again.","## Show the Build After a Deploy\n`pnpm phoenix deploy` starts a build on the server and waits for it to go live. Right after `Build <id> started` it prints `Follow the build in Studio: <url>`. Run the deploy so that you can read its output while it runs, and open that link in the user's browser, quoted as above, as soon as it appears, so they can follow the build they just triggered. When the line is missing, the sign-in is older than the link: tell the user the build is listed under Deployments in Studio, and that `pnpm phoenix login <workspace>` brings the link back.","## Code Review\nUse the `phoenix-review` skill to review code changes before committing.",t.sdkDocumentation]});
1
+ import{renderAgentSkill as e}from"./agent-skill.js";import{sdkGuidanceSections as t}from"./sdk-content.js";export const followsCanaryBuilds=e=>!e.useWorkspace&&e.sdkVersion.includes("-develop.");export const generatePhoenixSkill=({checkLatestCanary:n})=>e({name:"phoenix",description:`Phoenix SDK project conventions, covering project structure, architecture principles, key patterns, file and export rules, webhook membership, versioning the domain model once it is deployed, showing the user the dev preview and the build of a deploy, the run evaluation that follows a live deploy, and how to search the SDK documentation${n?", and the internal-beta check for the latest canary SDK build":""}. Use before writing or changing specs or source code in this Phoenix configurator project.`,sections:[...n?["## Stay on the Latest Canary\nDuring the internal beta this project uses the `canary` build of the SDK, which is published from every change to the platform. Once per conversation, before you change anything, compare the installed version with the newest canary:\n```bash\npnpm list @salsita-ai/sdk --depth 0\nnpm view @salsita-ai/sdk@canary version\n```\nWhen they differ, tell the user which version you are moving to, then update the project and regenerate its files:\n```bash\npnpm add @salsita-ai/sdk@canary\npnpm phoenix sync\n```\n`sync` rewrites the Phoenix skills, so read the ones you use again after it. When the user runs a globally installed `phoenix`, update that too with `npm install -g @salsita-ai/sdk@canary`. If the project no longer builds after the update, stop and tell the user; the `phoenix-feedback` skill covers reporting it."]:[],t.projectStructure,t.architecturePrinciples,t.keyPatterns,t.fileConventions,t.exportRules,t.webhookMembership,t.doNotEdit,t.modelVersioning,"## Show the Preview While You Work\nThe user should see the configurator change as you build it. Before your first change in a conversation, make sure `pnpm phoenix dev` is running (start it in the background when it is not) and open the address it prints as `Preview: <url>` (also written to `.phoenix/phoenix-dev.log`) in the user's browser: `open '<url>'` on macOS, `xdg-open '<url>'` on Linux, `Start-Process '<url>'` in PowerShell, `start \"\" \"<url>\"` in cmd. Keep the quotes, the shell would otherwise read a `?` in the address as a pattern. The log keeps the lines of earlier runs, so when you have just started dev, wait for the `Preview:` line written after your start and use the last one. Open it once; the preview reloads on every change, so tell the user to keep that tab open instead of opening it again.",'## Show the Build After a Deploy\n`pnpm phoenix deploy` starts a build on the server and waits for it to go live. Right after `Build <id> started` it prints `Follow the build in Studio: <url>`. Run the deploy so that you can read its output while it runs, and open that link in the user\'s browser, quoted as above, as soon as it appears, so they can follow the build they just triggered. When the line is missing, the sign-in is older than the link: tell the user the build is listed under Deployments in Studio, and that `pnpm phoenix login <workspace>` brings the link back.\n\nWhen the deploy reports the configurator live, load the `phoenix-feedback` skill before you write your final message and follow its "Evaluate the Run" section, so the user is asked about the run while they are still there. It is asked once per conversation, and only once the user has nothing more to build.',"## Code Review\nUse the `phoenix-review` skill to review code changes before committing.",t.sdkDocumentation]});
@@ -25,6 +25,8 @@ export declare class LayoutSystem<ENTITIES extends DomainEntity<string>> {
25
25
  * whatever was true when it was created.
26
26
  */
27
27
  private get _toolbarHelpExists();
28
+ /** Whether the application spec has an `assistant` section; read on demand for the same reason. */
29
+ private get _hasAssistant();
28
30
  initialize: () => void;
29
31
  getCleanUpPropertyPageStateUpdates: (entityId: DomainEntityId<string>, state: DomainState<ENTITIES>) => import("#package/domain").ComponentModification<import("#package/domain").SystemComponentType<"layoutStateComponent", import("./layout-state.js").LayoutStateComponentState, "propertyPageStates">>[];
30
32
  getLayoutConfig: (state: DomainState<ENTITIES>) => {
@@ -1 +1 @@
1
- import{P as t,match as e}from"ts-pattern";import{SystemState as o,applicationConfigComponent as n,customSummaryPageComponent as a,entityHeaderComponent as i,navigationStepsComponent as r,propertyPageComponent as s,sidebarTabsComponent as l,simpleSummaryComponent as d,toolbarComponent as c}from"#package/domain";import{layoutStateComponent as g}from"./layout-state.js";import{toolbarHelpElementName as u}from"./toolbar-help-element.js";export class LayoutSystem{_context;_toolbarCache=new WeakMap;state;constructor(t){this._context=t,this.state=new o({...this._context,selectors:this._context.domainContextSelectors,componentType:g,getInitialState:()=>({propertyPageStates:{}})})}get _toolbarHelpExists(){return void 0!==this._context.specSelectors.getElementSlots().layoutElements[u]}initialize=()=>{this.state.initialize()};getCleanUpPropertyPageStateUpdates=(t,e)=>this.state.getUpdates(e=>{if(!e.propertyPageStates[t])return null;let o={...e.propertyPageStates};return delete o[t],{...e,propertyPageStates:o}},e);getLayoutConfig=t=>{let e=this._context.domainContextSelectors.getComponentReader().readFirstRootComponent({componentTypes:[n]},t),o=e?.state.shareDisabled??!1,a=o||(e?.state.shareOwnerLinkDisabled??!1),i=o||(e?.state.shareReadRevisionLinkDisabled??!1);return{layout:e?.state.layout??"universal",ignoreSelectionInSidebar:e?.state.ignoreSelectionInSidebar??!1,miniSidebar:e?.state.miniSidebar??!1,splitSidebar:e?.state.splitSidebar??!1,showSelectionBackButton:e?.state.showSelectionBackButton??!0,shareDisabled:a&&i,availableLocales:e?.state.availableLocales??["en"],indicatorSwitch:e?.state.indicatorSwitch??!0}};getSidebarSummaryPage=t=>{let e=this._context.domainContextSelectors.getNavigationSystem().getActiveNavigationState(t),o=this._context.domainContextSelectors.getComponentReader().readFirstRootComponent({componentTypes:[a,d]},t),n=e===o?.state.summaryNavigationState,i=o?.state.summaryNavigationState;return{page:o,isSummaryActive:n,summaryNavigationState:i}};getEnterSidebarSummaryUpdates=t=>{let{summaryNavigationState:e}=this.getSidebarSummaryPage(t);return e?[this._context.domainContextSelectors.getNavigationSystem().getPushNavigationUpdate(e,t)]:[]};getLeaveSidebarSummaryUpdates=t=>{let e=this._context.domainContextSelectors.getNavigationSystem().getPopNavigationUpdate(t);if(e)return[e];let o=this._context.domainContextSelectors.getNavigationStates().at(0);return o?(this._context.domainServicesSelectors.getLogger().error("There is no navigation state to pop, falling back to the initial navigation state. Check whether the history stack is not broken by some custom navigation logic."),[this._context.domainContextSelectors.getNavigationSystem().getNavigationUpdate(o)]):(this._context.domainServicesSelectors.getLogger().error("There is no navigation state to pop and no initial navigation state to fallback to, this should not happen"),[])};getPushSidebarStackPageUpdates=(t,e,o,n)=>this.state.getUpdates(n=>{let a=n.propertyPageStates[t]??{customState:{},stackPages:[]};return{...n,propertyPageStates:{...n.propertyPageStates,[t]:{...a,stackPages:[...a.stackPages,{props:o,component:e}]}}}},n);getPopSidebarStackPageUpdates=(t,e)=>this.state.getUpdates(e=>{let o=e.propertyPageStates[t]??{customState:{},stackPages:[]};return{...e,propertyPageStates:{...e.propertyPageStates,[t]:{...o,stackPages:o.stackPages.slice(0,-1)}}}},e);getSidebarBackUpdates=t=>{let e=this._context.domainContextSelectors.getNavigationSystem().getHasStackToPop(t),o=this._context.domainContextSelectors.getNavigationSystem().getIsRootSelected(t),{renderedStackedPage:n,summaryPage:a,activePropertyPageId:i}=this.getSidebarContent(t);if(a)return this.getLeaveSidebarSummaryUpdates(t);if(n&&i)return this.getPopSidebarStackPageUpdates(i,t);if(e){let e=this._context.domainContextSelectors.getNavigationSystem().getPopNavigationUpdate(t);return e?[e]:[]}return o?[]:[this._context.domainContextSelectors.getNavigationSystem().getDeselectUpdate()]};getSidebarContent=t=>{let e=this._context.domainContextSelectors.getNavigationSystem().getActiveSelectionState(t),o=this.state.get(t),n=this.getLayoutConfig(t),a=this._context.domainContextSelectors.getComponentReader().readManyComponents({componentTypes:[l],entityIds:[e]},t).some(t=>t.state.tabs.length>0),r=n.miniSidebar&&!a;r&&a&&this._context.domainServicesSelectors.getLogger().warn("Mini sidebar is not active because the layout contains tabs. Only layouts without tabs can have mini sidebar.");let d=this._context.domainContextSelectors.getComponentReader().readManyComponents({componentTypes:[s]},t),{isSummaryActive:c,page:g}=this.getSidebarSummaryPage(t),u=this._context.domainContextSelectors.getComponentReader().readManyComponents({componentTypes:[i]},t),p=this._context.domainContextSelectors.getCompositionProjections().getTree(t).map.get(e);if(!p)throw Error(`Selected entity ${e} not found in composition tree`);let m=this._context.domainContextSelectors.getCompositionProjections().getTree(t).map.get(t.rootEntityId);if(!m)throw Error(`Root entity ${t.rootEntityId} not found in composition tree`);let S=(n.ignoreSelectionInSidebar?[m]:[p,...p.ancestors()]).map(t=>{let e=n.ignoreSelectionInSidebar?d:d.filter(e=>e.componentInstance.entityId===t.id),o=u.filter(e=>e.componentInstance.entityId===t.id);return{id:t.id,propertyPages:e,header:o[0]??null}}),h=S[0]?.id,b=(h?o.propertyPageStates[h]?.stackPages??[]:[]).at(-1)??null,k=this._context.domainContextSelectors.getNavigationSystem().getHasStackToPop(t),x=this._context.domainContextSelectors.getNavigationSystem().getIsRootSelected(t),y=c&&k||k||!!b||!x&&n.showSelectionBackButton&&!n.ignoreSelectionInSidebar;return c?{summaryPage:g,propertyPages:[],isBackAvailable:y,hasMiniSidebar:r,hasSplitSidebar:n.splitSidebar}:{summaryPage:null,propertyPages:S,renderedStackedPage:b,activePropertyPageId:h,isBackAvailable:y,hasMiniSidebar:r,hasSplitSidebar:n.splitSidebar}};getToolbarState=o=>{let n=this._toolbarHelpExists,a=this._toolbarCache.get(o);if(a&&a.toolbarHelpExists===n)return a.result;let i=this._context.domainContextSelectors.getExpressionContext(),r=this._context.domainContextSelectors.getNavigationSystem().getActiveFullscreenState(o),s="read"===i.mode,l=this._context.domainContextSelectors.getComponentReader().readFirstRootComponent({componentTypes:[c]},o),d=this._context.domainContextSelectors.getDimensionSystem().getDimensionConfig(o),g=this.getLayoutConfig(o),u="read"===this._context.domainContextSelectors.getExpressionContext().mode,p=g.availableLocales.length>1,m=!u&&!g.shareDisabled,S=g.indicatorSwitch,h=d.availableDimensions.length>1||S,b=e(l).returnType().with(t.nonNullable,t=>t.state).with(t.nullish,()=>e([r,s]).returnType().with([!1,!1],()=>({desktop:[{kind:"toolbarButtonGroup",children:[{kind:"enterFullscreenToolbarButton"}]},{kind:"toolbarButtonGroup",children:[{kind:"resetCameraToolbarButton"}]},...p?[{kind:"toolbarButtonGroup",children:[{kind:"languageToolbarButton"}]}]:[],...m||h?[{kind:"toolbarButtonGroup",children:[...m?[{kind:"shareToolbarButton"}]:[],...h?[{kind:"measurementsToolbarButton"}]:[],...n?[{kind:"helpToolbarButton"}]:[]]}]:[],{kind:"toolbarButtonGroup",children:[{kind:"undoToolbarButton"},{kind:"redoToolbarButton"}]}],mobile:[{kind:"toolbarButtonGroup",children:[{kind:"undoToolbarButton"},{kind:"redoToolbarButton"}]},{kind:"toolbarButtonGroup",children:[{kind:"resetCameraToolbarButton"}]},{kind:"toolbarButtonGroup",children:[{kind:"enterFullscreenToolbarButton"}]}],mobileMenu:[...m?[{kind:"shareMenuAction"}]:[],...h?[{kind:"measurementsMenuAction"}]:[],...p?[{kind:"languageMenuAction"}]:[],...n?[{kind:"helpMenuAction"}]:[]]})).with([!0,!1],()=>({desktop:[{kind:"toolbarButtonGroup",children:[{kind:"exitFullscreenToolbarButton"}]}],mobile:[{kind:"toolbarButtonGroup",children:[{kind:"exitFullscreenToolbarButton"}]}],mobileMenu:[]})).with([!1,!0],()=>({desktop:[{kind:"toolbarButtonGroup",children:[{kind:"enterFullscreenToolbarButton"}]},{kind:"toolbarButtonGroup",children:[{kind:"resetCameraToolbarButton"}]},...p?[{kind:"toolbarButtonGroup",children:[{kind:"languageToolbarButton"}]}]:[],...h?[{kind:"toolbarButtonGroup",children:[...h?[{kind:"measurementsToolbarButton"}]:[]]}]:[],...n?[{kind:"helpToolbarButton"}]:[]],mobile:[{kind:"toolbarButtonGroup",children:[{kind:"resetCameraToolbarButton"}]},{kind:"toolbarButtonGroup",children:[{kind:"enterFullscreenToolbarButton"}]}],mobileMenu:[...h?[{kind:"measurementsMenuAction"}]:[],...p?[{kind:"languageMenuAction"}]:[],...n?[{kind:"helpMenuAction"}]:[]]})).with([!0,!0],()=>({desktop:[{kind:"toolbarButtonGroup",children:[{kind:"exitFullscreenToolbarButton"}]}],mobile:[{kind:"toolbarButtonGroup",children:[{kind:"exitFullscreenToolbarButton"}]}],mobileMenu:[]})).exhaustive()).exhaustive();return this._toolbarCache.set(o,{toolbarHelpExists:n,result:b}),b};getNavigationSteps=t=>{let e=this._context.domainContextSelectors.getComponentReader().readFirstRootComponent({componentTypes:[r]},t);return e?.state.steps??[]}}
1
+ import{P as t,match as e}from"ts-pattern";import{SystemState as o,applicationConfigComponent as n,customSummaryPageComponent as a,entityHeaderComponent as i,navigationStepsComponent as r,propertyPageComponent as s,sidebarTabsComponent as l,simpleSummaryComponent as d,toolbarComponent as c}from"#package/domain";import{layoutStateComponent as g}from"./layout-state.js";import{toolbarHelpElementName as u}from"./toolbar-help-element.js";export class LayoutSystem{_context;_toolbarCache=new WeakMap;state;constructor(t){this._context=t,this.state=new o({...this._context,selectors:this._context.domainContextSelectors,componentType:g,getInitialState:()=>({propertyPageStates:{}})})}get _toolbarHelpExists(){return void 0!==this._context.specSelectors.getElementSlots().layoutElements[u]}get _hasAssistant(){return void 0!==this._context.specSelectors.getAssistant()}initialize=()=>{this.state.initialize()};getCleanUpPropertyPageStateUpdates=(t,e)=>this.state.getUpdates(e=>{if(!e.propertyPageStates[t])return null;let o={...e.propertyPageStates};return delete o[t],{...e,propertyPageStates:o}},e);getLayoutConfig=t=>{let e=this._context.domainContextSelectors.getComponentReader().readFirstRootComponent({componentTypes:[n]},t),o=e?.state.shareDisabled??!1,a=o||(e?.state.shareOwnerLinkDisabled??!1),i=o||(e?.state.shareReadRevisionLinkDisabled??!1);return{layout:e?.state.layout??"universal",ignoreSelectionInSidebar:e?.state.ignoreSelectionInSidebar??!1,miniSidebar:e?.state.miniSidebar??!1,splitSidebar:e?.state.splitSidebar??!1,showSelectionBackButton:e?.state.showSelectionBackButton??!0,shareDisabled:a&&i,availableLocales:e?.state.availableLocales??["en"],indicatorSwitch:e?.state.indicatorSwitch??!0}};getSidebarSummaryPage=t=>{let e=this._context.domainContextSelectors.getNavigationSystem().getActiveNavigationState(t),o=this._context.domainContextSelectors.getComponentReader().readFirstRootComponent({componentTypes:[a,d]},t),n=e===o?.state.summaryNavigationState,i=o?.state.summaryNavigationState;return{page:o,isSummaryActive:n,summaryNavigationState:i}};getEnterSidebarSummaryUpdates=t=>{let{summaryNavigationState:e}=this.getSidebarSummaryPage(t);return e?[this._context.domainContextSelectors.getNavigationSystem().getPushNavigationUpdate(e,t)]:[]};getLeaveSidebarSummaryUpdates=t=>{let e=this._context.domainContextSelectors.getNavigationSystem().getPopNavigationUpdate(t);if(e)return[e];let o=this._context.domainContextSelectors.getNavigationStates().at(0);return o?(this._context.domainServicesSelectors.getLogger().error("There is no navigation state to pop, falling back to the initial navigation state. Check whether the history stack is not broken by some custom navigation logic."),[this._context.domainContextSelectors.getNavigationSystem().getNavigationUpdate(o)]):(this._context.domainServicesSelectors.getLogger().error("There is no navigation state to pop and no initial navigation state to fallback to, this should not happen"),[])};getPushSidebarStackPageUpdates=(t,e,o,n)=>this.state.getUpdates(n=>{let a=n.propertyPageStates[t]??{customState:{},stackPages:[]};return{...n,propertyPageStates:{...n.propertyPageStates,[t]:{...a,stackPages:[...a.stackPages,{props:o,component:e}]}}}},n);getPopSidebarStackPageUpdates=(t,e)=>this.state.getUpdates(e=>{let o=e.propertyPageStates[t]??{customState:{},stackPages:[]};return{...e,propertyPageStates:{...e.propertyPageStates,[t]:{...o,stackPages:o.stackPages.slice(0,-1)}}}},e);getSidebarBackUpdates=t=>{let e=this._context.domainContextSelectors.getNavigationSystem().getHasStackToPop(t),o=this._context.domainContextSelectors.getNavigationSystem().getIsRootSelected(t),{renderedStackedPage:n,summaryPage:a,activePropertyPageId:i}=this.getSidebarContent(t);if(a)return this.getLeaveSidebarSummaryUpdates(t);if(n&&i)return this.getPopSidebarStackPageUpdates(i,t);if(e){let e=this._context.domainContextSelectors.getNavigationSystem().getPopNavigationUpdate(t);return e?[e]:[]}return o?[]:[this._context.domainContextSelectors.getNavigationSystem().getDeselectUpdate()]};getSidebarContent=t=>{let e=this._context.domainContextSelectors.getNavigationSystem().getActiveSelectionState(t),o=this.state.get(t),n=this.getLayoutConfig(t),a=this._context.domainContextSelectors.getComponentReader().readManyComponents({componentTypes:[l],entityIds:[e]},t).some(t=>t.state.tabs.length>0),r=n.miniSidebar&&!a;r&&a&&this._context.domainServicesSelectors.getLogger().warn("Mini sidebar is not active because the layout contains tabs. Only layouts without tabs can have mini sidebar.");let d=this._context.domainContextSelectors.getComponentReader().readManyComponents({componentTypes:[s]},t),{isSummaryActive:c,page:g}=this.getSidebarSummaryPage(t),u=this._context.domainContextSelectors.getComponentReader().readManyComponents({componentTypes:[i]},t),p=this._context.domainContextSelectors.getCompositionProjections().getTree(t).map.get(e);if(!p)throw Error(`Selected entity ${e} not found in composition tree`);let m=this._context.domainContextSelectors.getCompositionProjections().getTree(t).map.get(t.rootEntityId);if(!m)throw Error(`Root entity ${t.rootEntityId} not found in composition tree`);let h=(n.ignoreSelectionInSidebar?[m]:[p,...p.ancestors()]).map(t=>{let e=n.ignoreSelectionInSidebar?d:d.filter(e=>e.componentInstance.entityId===t.id),o=u.filter(e=>e.componentInstance.entityId===t.id);return{id:t.id,propertyPages:e,header:o[0]??null}}),S=h[0]?.id,b=(S?o.propertyPageStates[S]?.stackPages??[]:[]).at(-1)??null,k=this._context.domainContextSelectors.getNavigationSystem().getHasStackToPop(t),x=this._context.domainContextSelectors.getNavigationSystem().getIsRootSelected(t),y=c&&k||k||!!b||!x&&n.showSelectionBackButton&&!n.ignoreSelectionInSidebar;return c?{summaryPage:g,propertyPages:[],isBackAvailable:y,hasMiniSidebar:r,hasSplitSidebar:n.splitSidebar}:{summaryPage:null,propertyPages:h,renderedStackedPage:b,activePropertyPageId:S,isBackAvailable:y,hasMiniSidebar:r,hasSplitSidebar:n.splitSidebar}};getToolbarState=o=>{let n=this._toolbarHelpExists,a=this._hasAssistant,i=this._toolbarCache.get(o);if(i&&i.toolbarHelpExists===n&&i.hasAssistant===a)return i.result;let r=this._context.domainContextSelectors.getExpressionContext(),s=this._context.domainContextSelectors.getNavigationSystem().getActiveFullscreenState(o),l="read"===r.mode,d=this._context.domainContextSelectors.getComponentReader().readFirstRootComponent({componentTypes:[c]},o),g=this._context.domainContextSelectors.getDimensionSystem().getDimensionConfig(o),u=this.getLayoutConfig(o),p="read"===this._context.domainContextSelectors.getExpressionContext().mode,m=u.availableLocales.length>1,h=!p&&!u.shareDisabled,S=u.indicatorSwitch,b=g.availableDimensions.length>1||S,k=a?[{kind:"toolbarButtonGroup",children:[{kind:"assistantToolbarButton"}]}]:[],x=e(d).returnType().with(t.nonNullable,t=>t.state).with(t.nullish,()=>e([s,l]).returnType().with([!1,!1],()=>({desktop:[{kind:"toolbarButtonGroup",children:[{kind:"enterFullscreenToolbarButton"}]},{kind:"toolbarButtonGroup",children:[{kind:"resetCameraToolbarButton"}]},...m?[{kind:"toolbarButtonGroup",children:[{kind:"languageToolbarButton"}]}]:[],...h||b?[{kind:"toolbarButtonGroup",children:[...h?[{kind:"shareToolbarButton"}]:[],...b?[{kind:"measurementsToolbarButton"}]:[],...n?[{kind:"helpToolbarButton"}]:[]]}]:[],{kind:"toolbarButtonGroup",children:[{kind:"undoToolbarButton"},{kind:"redoToolbarButton"}]},...k],mobile:[{kind:"toolbarButtonGroup",children:[{kind:"undoToolbarButton"},{kind:"redoToolbarButton"}]},{kind:"toolbarButtonGroup",children:[{kind:"resetCameraToolbarButton"}]},{kind:"toolbarButtonGroup",children:[{kind:"enterFullscreenToolbarButton"}]},...k],mobileMenu:[...h?[{kind:"shareMenuAction"}]:[],...b?[{kind:"measurementsMenuAction"}]:[],...m?[{kind:"languageMenuAction"}]:[],...n?[{kind:"helpMenuAction"}]:[]]})).with([!0,!1],()=>({desktop:[{kind:"toolbarButtonGroup",children:[{kind:"exitFullscreenToolbarButton"}]}],mobile:[{kind:"toolbarButtonGroup",children:[{kind:"exitFullscreenToolbarButton"}]}],mobileMenu:[]})).with([!1,!0],()=>({desktop:[{kind:"toolbarButtonGroup",children:[{kind:"enterFullscreenToolbarButton"}]},{kind:"toolbarButtonGroup",children:[{kind:"resetCameraToolbarButton"}]},...m?[{kind:"toolbarButtonGroup",children:[{kind:"languageToolbarButton"}]}]:[],...b?[{kind:"toolbarButtonGroup",children:[...b?[{kind:"measurementsToolbarButton"}]:[]]}]:[],...n?[{kind:"helpToolbarButton"}]:[],...k],mobile:[{kind:"toolbarButtonGroup",children:[{kind:"resetCameraToolbarButton"}]},{kind:"toolbarButtonGroup",children:[{kind:"enterFullscreenToolbarButton"}]},...k],mobileMenu:[...b?[{kind:"measurementsMenuAction"}]:[],...m?[{kind:"languageMenuAction"}]:[],...n?[{kind:"helpMenuAction"}]:[]]})).with([!0,!0],()=>({desktop:[{kind:"toolbarButtonGroup",children:[{kind:"exitFullscreenToolbarButton"}]}],mobile:[{kind:"toolbarButtonGroup",children:[{kind:"exitFullscreenToolbarButton"}]}],mobileMenu:[]})).exhaustive()).exhaustive();return this._toolbarCache.set(o,{toolbarHelpExists:n,hasAssistant:a,result:x}),x};getNavigationSteps=t=>{let e=this._context.domainContextSelectors.getComponentReader().readFirstRootComponent({componentTypes:[r]},t);return e?.state.steps??[]}}
@@ -1,20 +1,30 @@
1
1
  import { type UIMessage } from '@tanstack/ai-react';
2
- /** Experimental assistant chat state and actions of the current configuration. */
2
+ /** Assistant chat state and actions of the current configuration. */
3
3
  export interface AssistantChatState {
4
4
  /** The thread as painted: hydrated from the server, then streamed. */
5
5
  readonly messages: ReadonlyArray<UIMessage>;
6
6
  /** True while a reply streams or a browser tool runs. */
7
7
  readonly isLoading: boolean;
8
8
  readonly error: Error | undefined;
9
+ /**
10
+ * HTTP status of the latest request the server answered with a failure, for telling a refusal from
11
+ * a dropped connection when `error` is set. Nothing when the latest answered request succeeded or
12
+ * the server was never reached.
13
+ */
14
+ readonly failedStatus: number | undefined;
15
+ /** False when the server said this session may read the thread but not add to it. */
16
+ readonly isContinuable: boolean;
17
+ /** False until the first request of the chat, the thread read, was answered or failed. */
18
+ readonly isLoaded: boolean;
9
19
  /** Sends a user message stamped with its send time. */
10
20
  readonly send: (content: string) => Promise<void>;
11
21
  /** Stops the current reply in the browser and on the server. */
12
22
  readonly stop: () => Promise<void>;
13
23
  }
14
24
  /**
15
- * Experimental: the conversational assistant of the current configuration. The server owns the
16
- * thread — the chat hydrates from it on mount and rejoins a reply still streaming — so the hook
17
- * keeps nothing in browser storage. Each message carries its send time for the model. The model's
18
- * `query` and `apply` calls run here, in the browser, against the configurator this hook is rendered in.
25
+ * The conversational assistant of the current configuration. The server owns the thread — the chat
26
+ * hydrates from it on mount and rejoins a reply still streaming — so the hook keeps nothing in
27
+ * browser storage. Each message carries its send time for the model. The model's `query` and
28
+ * `apply` calls run here, in the browser, against the configurator this hook is rendered in.
19
29
  */
20
30
  export declare const useAssistantChat: () => AssistantChatState;
@@ -1 +1 @@
1
- import{fetchServerSentEvents as t,useChat as e}from"@tanstack/ai-react";import{useCallback as a,useMemo as n}from"react";import{useApplicationRuntimeSelector as s,useAuthorizedClient as r,useConfigurationId as o}from"#package/react-sdk";import{useConfigurator as i}from"../application-runtime/index.js";export const useAssistantChat=()=>{let c=o(),p=r(),l=n(()=>p.getAssistantChatConnection(c),[p,c]),u=n(()=>{let e=async()=>({headers:await l.headers()}),a=t(l.url,e),n=t(`${l.url}/runs`,e);return{...a,joinRun:n.joinRun}},[l]),{assistantSystem:d}=i(),{messages:m,isLoading:h,error:f,sendMessage:g,stop:w,runId:y}=e({threadId:`assistant:${c}`,connection:u,persistence:!0,history:{pageSize:50},tools:s(d.getTools)});return{messages:m,isLoading:h,error:f,send:a(async t=>{let e={sentAt:new Date().toISOString()};await g({content:t,metadata:e})},[g]),stop:a(async()=>{w(),null!=y&&await fetch(`${l.url}/runs/${encodeURIComponent(y)}/cancel`,{method:"POST",headers:await l.headers()})},[l,y,w])}};
1
+ import{fetchServerSentEvents as t,useChat as e}from"@tanstack/ai-react";import{useCallback as a,useMemo as n,useState as r}from"react";import{useApplicationRuntimeSelector as s,useAuthorizedClient as o,useConfigurationId as i,useLogger as c}from"#package/react-sdk";import{useConfigurator as l}from"../application-runtime/index.js";export const useAssistantChat=()=>{let u=i(),h=o(),d=c(),[p,m]=r(),[f,w]=r(!0),[y,g]=r(!1),C=a(()=>h.getAssistantChatConnection(u),[h,u]),k=n(()=>{let e=async(t,e)=>{let a=await fetch(t,e).finally(()=>g(!0)),n=a.headers.get("x-assistant-thread-continuable");return m(a.ok?void 0:a.status),null!=n&&w("false"!==n),a},a=async()=>({headers:await C().headers(),fetchClient:e}),n=t(()=>C().url,a),r=t(()=>`${C().url}/runs`,a);return{...n,joinRun:r.joinRun}},[C]),{assistantSystem:S}=l(),{messages:$,isLoading:j,error:x,sendMessage:A,stop:I,runId:R}=e({threadId:`assistant:${u}`,connection:k,persistence:!0,history:{pageSize:50},tools:s(S.getTools)}),v=a(async t=>{let e={sentAt:new Date().toISOString()};await A({content:t,metadata:e})},[A]),O=a(async()=>{if(I(),null!=R)try{let t=C();await fetch(`${t.url}/runs/${encodeURIComponent(R)}/cancel`,{method:"POST",headers:await t.headers()})}catch(t){d.warn("Could not cancel the assistant run on the server",{error:t})}},[C,d,R,I]);return n(()=>({messages:$,isLoading:j,error:x,failedStatus:p,isContinuable:f,isLoaded:y,send:v,stop:O}),[$,j,x,p,f,y,v,O])};
@@ -1,25 +1,25 @@
1
1
  import { type EntityId } from '#package/domain';
2
- declare const CloseConfiguratorClicked: () => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
3
- declare const PreviousStepButtonClicked: () => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
4
- declare const NextStepButtonClicked: () => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
5
- declare const FinalizeButtonClicked: () => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
6
- declare const SummaryButtonClicked: () => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
7
- declare const TopNavigationStepClicked: (step: string) => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
8
- declare const HorizontalTabClicked: (tab: string) => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
9
- declare const GoToProductsClicked: () => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
10
- declare const ExpandedViewClosed: () => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
11
- declare const ProductSelected: (productType: string, productName: string) => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
12
- declare const SelectionHotspotClicked: (id: string) => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
13
- declare const RedoButtonClicked: () => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
14
- declare const UndoButtonClicked: () => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
15
- declare const GlobalDimensionsButtonClicked: (dimensionUnit: string) => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
16
- declare const MeasurementsMenuToggled: () => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
17
- declare const MeasurementsMenuClosed: () => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
18
- declare const DimensionIndicatorsToggled: () => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
19
- declare const FullScreenModeToggled: () => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
20
- declare const HelpDialogOpened: () => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
21
- declare const HelpDialogClosed: () => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
22
- declare const EntityDeleteHotspotClicked: (id: EntityId) => import("../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
2
+ declare const CloseConfiguratorClicked: () => import("#package/domain").UserActionEvent;
3
+ declare const PreviousStepButtonClicked: () => import("#package/domain").UserActionEvent;
4
+ declare const NextStepButtonClicked: () => import("#package/domain").UserActionEvent;
5
+ declare const FinalizeButtonClicked: () => import("#package/domain").UserActionEvent;
6
+ declare const SummaryButtonClicked: () => import("#package/domain").UserActionEvent;
7
+ declare const TopNavigationStepClicked: (step: string) => import("#package/domain").UserActionEvent;
8
+ declare const HorizontalTabClicked: (tab: string) => import("#package/domain").UserActionEvent;
9
+ declare const GoToProductsClicked: () => import("#package/domain").UserActionEvent;
10
+ declare const ExpandedViewClosed: () => import("#package/domain").UserActionEvent;
11
+ declare const ProductSelected: (productType: string, productName: string) => import("#package/domain").UserActionEvent;
12
+ declare const SelectionHotspotClicked: (id: string) => import("#package/domain").UserActionEvent;
13
+ declare const RedoButtonClicked: () => import("#package/domain").UserActionEvent;
14
+ declare const UndoButtonClicked: () => import("#package/domain").UserActionEvent;
15
+ declare const GlobalDimensionsButtonClicked: (dimensionUnit: string) => import("#package/domain").UserActionEvent;
16
+ declare const MeasurementsMenuToggled: () => import("#package/domain").UserActionEvent;
17
+ declare const MeasurementsMenuClosed: () => import("#package/domain").UserActionEvent;
18
+ declare const DimensionIndicatorsToggled: () => import("#package/domain").UserActionEvent;
19
+ declare const FullScreenModeToggled: () => import("#package/domain").UserActionEvent;
20
+ declare const HelpDialogOpened: () => import("#package/domain").UserActionEvent;
21
+ declare const HelpDialogClosed: () => import("#package/domain").UserActionEvent;
22
+ declare const EntityDeleteHotspotClicked: (id: EntityId) => import("#package/domain").UserActionEvent;
23
23
  export declare const FrontendAnalyticsFactory: {
24
24
  CloseConfiguratorClicked: typeof CloseConfiguratorClicked;
25
25
  PreviousStepButtonClicked: typeof PreviousStepButtonClicked;
@@ -1,5 +1,5 @@
1
- declare const PerspectiveCameraDragEvent: () => import("../../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
2
- declare const OrthographicCameraDragEvent: () => import("../../../domain/domain-context/analytics-system/analytics-contracts").UserActionEvent;
1
+ declare const PerspectiveCameraDragEvent: () => import("#package/domain").UserActionEvent;
2
+ declare const OrthographicCameraDragEvent: () => import("#package/domain").UserActionEvent;
3
3
  export declare const SceneAnalyticsFactory: {
4
4
  PerspectiveCameraDragEvent: typeof PerspectiveCameraDragEvent;
5
5
  OrthographicCameraDragEvent: typeof OrthographicCameraDragEvent;
@@ -0,0 +1,11 @@
1
+ import type { AssistantToolItem } from './thread-items.js';
2
+ export interface AssistantToolChipsProps {
3
+ readonly tools: readonly AssistantToolItem[];
4
+ /** True while the reply runs and this row is its newest block: the assistant is at work on it. */
5
+ readonly isActive: boolean;
6
+ }
7
+ /**
8
+ * A row of notes on what the assistant did in a reply, one per tool call. A click on a note logs
9
+ * the input and the result of its call to the console.
10
+ */
11
+ export declare const AssistantToolChips: import("react").MemoExoticComponent<({ tools, isActive }: AssistantToolChipsProps) => import("react/jsx-runtime").JSX.Element>;
@@ -0,0 +1 @@
1
+ import{jsx as t,jsxs as e}from"react/jsx-runtime";import{Tooltip as a,styled as i}from"@mui/material";import{memo as n,useCallback as s}from"react";import{match as o}from"ts-pattern";import{useLocalizable as r}from"#package/react-sdk";import{PhxIcon as l}from"#package/ui-sdk";import{AssistantPanelName as d}from"./theme.js";let c={id:"sdk.assistant.tool.updateDesign.label",message:"Update design"},m={id:"sdk.assistant.tool.readDesign.label",message:"Read design"},p={id:"sdk.assistant.tool.mistake.hint",message:"The assistant made a mistake."},h=i("div",{name:d,slot:"chips"})(({theme:{palette:t,spacing:e,typography:a}})=>({...a.caption,alignItems:"start",color:t.text.secondary,display:"grid",gap:e(.75),gridTemplateColumns:"min-content minmax(0, 1fr)"})),g=i("span",{name:d,slot:"activity"})(({theme:{typography:t}})=>({blockSize:`calc(${t.caption.lineHeight} * 1em)`,display:"grid",placeItems:"center"})),f=i(l,{name:d,slot:"activityIcon"})({fontSize:14,'[data-chips="active"] &':{animation:"phx-assistant-turn 2.4s linear infinite","@media (prefers-reduced-motion: reduce)":{animation:"none"}},"@keyframes phx-assistant-turn":{to:{rotate:"360deg"}}}),k=i("span",{name:d,slot:"chip"})(({theme:{palette:t,spacing:e}})=>({cursor:"default",display:"inline-block",whiteSpace:"nowrap","&:not(:last-child)::after":{color:t.text.secondary,content:'"\xb7"',display:"inline-block",marginInline:e(.75),textDecoration:"none"},'&[data-chip="failed"]':{color:t.error.main},'&[data-chip="flawed"]':{textDecoration:"line-through",textDecorationColor:t.error.main}})),u=n(({tool:e})=>{let{chip:i,input:n,result:l}=e,d=r(c),h=r(m),g=r(p),f=o(i.label).with({kind:"text"},({text:t})=>t).with({kind:"updateDesign"},()=>d).with({kind:"readDesign"},()=>h).exhaustive(),u=s(()=>{console.info(`Assistant tool call "${f}"`,{input:n,result:l})},[n,f,l]);return t(a,{title:"failed"===i.state||"flawed"===i.state?g:"",placement:"top-start",children:t(k,{onClick:u,"data-chip":i.state,children:f})})});export const AssistantToolChips=n(({tools:a,isActive:i})=>e(h,{"data-chips":i?"active":"done","aria-busy":i,children:[t(g,{children:t(f,{name:"Gear"})}),t("div",{children:a.map(e=>t(u,{tool:e},e.id))})]}));
@@ -0,0 +1,11 @@
1
+ import type { AssistantChatState } from '../../assistant/index.js';
2
+ export interface AssistantComposerProps {
3
+ readonly chat: AssistantChatState | undefined;
4
+ readonly onFocusChange: (isFocused: boolean) => void;
5
+ }
6
+ /**
7
+ * The message input of the panel. Enter sends and Shift+Enter adds a line. While a reply runs the
8
+ * send control is a Stop control and the input stays editable, so the next message can be written.
9
+ * A thread this session may not continue disables the input and says why.
10
+ */
11
+ export declare const AssistantComposer: ({ chat, onFocusChange }: AssistantComposerProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ import{jsx as e,jsxs as a}from"react/jsx-runtime";import{Trans as r}from"@lingui/react";import{IconButton as o,InputBase as i,Tooltip as t,styled as n}from"@mui/material";import{useCallback as s,useRef as d,useState as l}from"react";import{useLocalizable as m}from"#package/react-sdk";import{PhxIcon as c,useResponsive as p}from"#package/ui-sdk";import{AssistantPanelName as u}from"./theme.js";let b={id:"sdk.assistant.composer.placeholder",message:"Ask or tell me what to change…"},g={id:"sdk.assistant.composer.send.label",message:"Send"},h={id:"sdk.assistant.composer.stop.label",message:"Stop"},k=n("form",{name:u,slot:"composer"})(({theme:{palette:e,spacing:a}})=>({borderBlockStart:`1px solid ${e.divider}`,display:"grid",gap:a(.75),paddingBlock:a(1.25),paddingInline:`var(--phx-sidebar-padding, ${a(2)})`})),f=n("div",{name:u,slot:"inputFrame"})(({theme:{palette:e,spacing:a}})=>({alignItems:"flex-end",border:`1px solid ${e.divider}`,borderRadius:12,display:"flex",gap:a(1),paddingBlock:a(.5),paddingInline:`${a(1.5)} ${a(.5)}`,"&:focus-within":{borderColor:e.primary.main},"&:has(textarea:disabled)":{background:e.action.disabledBackground}})),x=n(i,{name:u,slot:"input"})(({theme:{spacing:e,typography:a}})=>({...a.body2,flex:1,paddingBlock:e(.75)})),v=n(o,{name:u,slot:"sendButton"})(({theme:{palette:e}})=>({background:e.primary.main,borderRadius:8,color:e.primary.contrastText,"&:hover":{background:e.primary.dark},"&.Mui-disabled":{background:e.action.disabledBackground,color:e.action.disabled}})),y=n(o,{name:u,slot:"stopButton"})(({theme:{palette:e}})=>({background:e.text.primary,borderRadius:8,color:e.background.paper,"&:hover":{background:e.text.secondary}})),B=n("span")({background:"currentColor",blockSize:10,borderRadius:2,inlineSize:10,margin:5}),C=n("div",{name:u,slot:"hint"})(({theme:{palette:e,typography:a}})=>({...a.caption,color:e.text.secondary}));export const AssistantComposer=({chat:o,onFocusChange:i})=>{let[n,u]=l(""),R=d(null),{isDesktop:S}=p(),w=m(b),z=m(g),$=m(h),j=o?.isLoading??!1,I=o?.isContinuable??!0,A=n.trim(),D=null!=o&&I&&!j&&A.length>0,P=s(()=>{o&&D&&(u(""),o.send(A),S||R.current?.blur())},[D,o,A,S]),T=s(e=>{e.preventDefault(),P()},[P]),E=s(e=>{"Enter"!==e.key||e.shiftKey||e.nativeEvent.isComposing||(e.preventDefault(),P())},[P]),F=s(()=>{o?.stop()},[o]);return a(k,{onSubmit:T,children:[!I&&e(C,{role:"status",children:e(r,{id:"sdk.assistant.composer.notContinuable",message:"This conversation can be read here, but it can no longer be continued."})}),a(f,{children:[e(x,{multiline:!0,maxRows:6,placeholder:w,value:n,disabled:!I,inputRef:R,onChange:e=>u(e.target.value),onKeyDown:E,onFocus:()=>i(!0),onBlur:()=>i(!1),inputProps:{"aria-label":w}}),j?e(t,{title:$,placement:"top",children:e(y,{size:"small","aria-label":$,onClick:F,children:e(B,{})})}):e(t,{title:z,placement:"top",children:e("span",{children:e(v,{type:"submit",size:"small","aria-label":z,disabled:!D,children:e(c,{name:"PaperPlaneRightBold",fontSize:"small"})})})})]}),e(C,{children:e(r,{id:"sdk.assistant.composer.disclaimer",message:"AI can be wrong. Check the result."})})]})};
@@ -0,0 +1,7 @@
1
+ export interface AssistantMarkdownProps {
2
+ readonly markdown: string;
3
+ /** True while the text is still arriving, so markup the model has not finished is completed. */
4
+ readonly isStreaming: boolean;
5
+ }
6
+ /** Assistant text of the thread: Markdown with lists, tables, links and code, without a bubble. */
7
+ export declare const AssistantMarkdown: import("react").MemoExoticComponent<({ markdown, isStreaming }: AssistantMarkdownProps) => import("react/jsx-runtime").JSX.Element>;
@@ -0,0 +1 @@
1
+ import{jsx as r}from"react/jsx-runtime";import{styled as e}from"@mui/material";import{memo as o}from"react";import a from"react-markdown";import i from"remark-gfm";import{completeStreamedMarkdown as n}from"./streamed-markdown.js";import{AssistantPanelName as t}from"./theme.js";let l=e("div",{name:t,slot:"assistantMessage"})(({theme:{palette:r,shape:e,spacing:o,typography:a}})=>({overflowWrap:"anywhere","& > :first-child":{marginBlockStart:0},"& > :last-child":{marginBlockEnd:0},"p, ul, ol, pre, blockquote":{marginBlock:o(1)},"h1, h2, h3, h4, h5, h6":{...a.subtitle2,marginBlock:o(1.5,.5)},"ul, ol":{paddingInlineStart:o(2.5)},a:{color:r.primary.main},code:{background:r.action.hover,borderRadius:e.borderRadius/2,fontFamily:"ui-monospace, Menlo, monospace",fontSize:"0.9em",paddingInline:o(.5)},pre:{background:r.action.hover,borderRadius:e.borderRadius,overflowX:"auto",padding:o(1),code:{background:"none",padding:0}},blockquote:{borderInlineStart:`2px solid ${r.divider}`,color:r.text.secondary,marginInline:0,paddingInlineStart:o(1.5)},"th, td":{borderBlockEnd:`1px solid ${r.divider}`,padding:o(.5,1),textAlign:"start",verticalAlign:"top"}})),d=e("div")({overflowX:"auto",table:{borderCollapse:"collapse",inlineSize:"100%"}}),m={a:({node:e,children:o,...a})=>r("a",{...a,target:"_blank",rel:"noopener noreferrer",children:o}),table:({node:e,children:o,...a})=>r(d,{children:r("table",{...a,children:o})})},s=[i],c=["img"];export const AssistantMarkdown=o(({markdown:e,isStreaming:o})=>r(l,{children:r(a,{components:m,remarkPlugins:s,disallowedElements:c,children:o?n(e):e})}));
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The attribute the panel carries on its root. The layouts key their styles on it: what the panel
3
+ * covers is hidden, and a sidebar shorter than its slot is stretched to the height the panel needs.
4
+ */
5
+ export declare const ASSISTANT_PANEL_SLOT = "slot[assistant]";
6
+ /**
7
+ * The built-in assistant panel: a header with the title and a close control, the thread and the
8
+ * composer. It covers the sidebar it is rendered in, and renders nothing while it is closed or when
9
+ * the application spec has no `assistant` section.
10
+ */
11
+ export declare const AssistantPanel: () => import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1 @@
1
+ import{jsx as e,jsxs as t}from"react/jsx-runtime";import{IconButton as a,Tooltip as s,Typography as n,styled as r}from"@mui/material";import{useCallback as o}from"react";import{useLocalizable as i}from"#package/react-sdk";import{PhxIcon as l}from"#package/ui-sdk";import{AssistantComposer as d}from"./AssistantComposer.js";import{useAssistantPanel as m,useAssistantPanelConversation as p}from"./AssistantPanelProvider.js";import{AssistantThread as c}from"./AssistantThread.js";import{AssistantPanelName as h}from"./theme.js";let u={id:"sdk.assistant.panel.title",message:"Assistant"},g={id:"sdk.assistant.panel.close.label",message:"Close assistant"};export const ASSISTANT_PANEL_SLOT="slot[assistant]";let b=r("section",{name:h,slot:"root"})(({theme:{palette:e}})=>({background:`var(--phx-sidebar-background, ${e.background.paper})`,border:"var(--phx-sidebar-border, 0)",borderRadius:"inherit",display:"grid",gridTemplateColumns:"minmax(0, 1fr)",gridTemplateRows:"min-content minmax(0, 1fr) min-content",inset:0,overflow:"hidden",position:"absolute",zIndex:1})),f=r("header",{name:h,slot:"header"})(({theme:{palette:e,spacing:t}})=>({alignItems:"center",borderBlockEnd:`1px solid ${e.divider}`,display:"flex",gap:t(1),justifyContent:"space-between",paddingBlock:t(1),paddingInline:`var(--phx-sidebar-padding, ${t(2)}) ${t(1)}`})),x=r(n,{name:h,slot:"title"})({fontWeight:600}),k=r(a,{name:h,slot:"closeButton"})({}),j=e=>{e.target instanceof Element&&e.target.closest("button")&&e.preventDefault()};export const AssistantPanel=()=>{let a=m(),n=p(),r=i(u),h=i(g),v=a?.setOpen,A=o(()=>{v?.(!1)},[v]);return a?.open&&n?t(b,{"data-sidebar":"slot[assistant]","aria-label":r,onMouseDown:j,children:[t(f,{children:[e(x,{variant:"subtitle1",component:"h2",children:r}),e(s,{title:h,placement:"top",children:e(k,{size:"small","aria-label":h,onClick:A,children:e(l,{name:"X"})})})]}),e(c,{chat:n.chat,onRetry:n.reloadThread}),e(d,{chat:n.chat,onFocusChange:a.setInputFocused})]}):null};
@@ -0,0 +1,42 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type AssistantChatState } from '../../assistant/index.js';
3
+ /**
4
+ * The built-in assistant panel of a universal layout as the layout and the toolbar button see it:
5
+ * whether it is open, and the few facts about it they render. It changes only when one of those does.
6
+ */
7
+ export interface AssistantPanelState {
8
+ readonly open: boolean;
9
+ readonly setOpen: (open: boolean) => void;
10
+ /** True when a reply finished while the panel was closed and the panel was not opened since. */
11
+ readonly hasUnreadReply: boolean;
12
+ /** True while the message input has focus; a phone layout gives the panel the whole screen then. */
13
+ readonly isInputFocused: boolean;
14
+ readonly setInputFocused: (isInputFocused: boolean) => void;
15
+ }
16
+ /** The conversation of the assistant panel. It changes with every chunk of a streaming reply. */
17
+ export interface AssistantPanelConversation {
18
+ /** Nothing until the chat of the current attempt has mounted. */
19
+ readonly chat: AssistantChatState | undefined;
20
+ /**
21
+ * Loads the thread from the server again, rejoining a reply that is still running there. It never
22
+ * sends a message again, because the server may already hold it; a message that did not reach the
23
+ * server is therefore gone from the thread and has to be written again.
24
+ */
25
+ readonly reloadThread: () => void;
26
+ }
27
+ /**
28
+ * The assistant panel of the layout this is rendered in, or nothing when the application spec has no
29
+ * `assistant` section or the layout does not carry the panel.
30
+ */
31
+ export declare const useAssistantPanel: () => AssistantPanelState | undefined;
32
+ /** The conversation of the assistant panel, under the same conditions as {@link useAssistantPanel}. */
33
+ export declare const useAssistantPanelConversation: () => AssistantPanelConversation | undefined;
34
+ export interface AssistantPanelProviderProps {
35
+ readonly children: ReactNode;
36
+ }
37
+ /**
38
+ * Owns the open state of the assistant panel and the single chat of the layout below it. The chat
39
+ * lives here and not in the panel, so a reply keeps streaming while the panel is closed. Without an
40
+ * `assistant` section in the application spec no chat is mounted and no request reaches the server.
41
+ */
42
+ export declare const AssistantPanelProvider: ({ children }: AssistantPanelProviderProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ import{jsx as e,jsxs as r}from"react/jsx-runtime";import{createContext as t,useCallback as s,useContext as o,useEffect as n,useMemo as a,useRef as i,useState as l}from"react";import{useLogger as d,useSpecAssistant as u}from"#package/react-sdk";import{useAssistantChat as c}from"../../assistant/index.js";let v=t(void 0),m=t(void 0);export const useAssistantPanel=()=>o(v);export const useAssistantPanelConversation=()=>o(m);let p=({onChange:e})=>{let r=c();return n(()=>{e(r)},[r,e]),n(()=>()=>{e(void 0)},[e]),null};export const AssistantPanelProvider=({children:t})=>{let o=u(),[c,x]=l(),[f,P]=l(!1),[h,j]=l(!1),[g,A]=l(0),[k,C]=l(!1),q=d(),L=s(e=>{P(e),e?j(!1):C(!1)},[]),S=c?.isLoading??!1,T=i(!1),b=s(()=>{T.current=!1,A(e=>e+1)},[]);n(()=>{!T.current||S||f||null==c||j(!0),T.current=S},[c,S,f]);let w=c?.error,y=c?.failedStatus;n(()=>{w&&q.error("The assistant request failed",{status:y},w)},[w,y,q]);let z=a(()=>o?{open:f,setOpen:L,hasUnreadReply:h,isInputFocused:k,setInputFocused:C}:void 0,[o,f,L,h,k]),B=a(()=>o?{chat:c,reloadThread:b}:void 0,[o,c,b]);return e(v.Provider,{value:z,children:r(m.Provider,{value:B,children:[o&&e(p,{onChange:x},g),t]})})};
@@ -0,0 +1,11 @@
1
+ import type { AssistantChatState } from '../../assistant/index.js';
2
+ export interface AssistantThreadProps {
3
+ readonly chat: AssistantChatState | undefined;
4
+ readonly onRetry: () => void;
5
+ }
6
+ /**
7
+ * The conversation: user bubbles, assistant Markdown, tool chips, and what the panel
8
+ * has to say itself, which is a greeting for an empty thread, a "Thinking…" line and a failure with
9
+ * its way out. It follows a streaming reply until the user scrolls up.
10
+ */
11
+ export declare const AssistantThread: ({ chat, onRetry }: AssistantThreadProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ import{jsx as e,jsxs as i}from"react/jsx-runtime";import{Trans as a}from"@lingui/react";import{Button as s,styled as t}from"@mui/material";import{useMemo as n}from"react";import{match as r}from"ts-pattern";import{PhxIcon as o}from"#package/ui-sdk";import{AssistantToolChips as l}from"./AssistantChips.js";import{AssistantMarkdown as d}from"./AssistantMarkdown.js";import{describeAssistantFailure as m}from"./assistant-failure.js";import{AssistantPanelName as p}from"./theme.js";import{buildThreadView as h}from"./thread-items.js";import{useFollowStream as c}from"./use-follow-stream.js";let g=t("div",{name:p,slot:"thread"})({display:"grid",position:"relative"}),u=t("div",{name:p,slot:"messages"})(({theme:{spacing:e,typography:i}})=>({...i.body2,display:"flex",flexDirection:"column",gap:e(1.5),overflowY:"auto",overscrollBehavior:"contain",paddingBlock:e(2),paddingInline:`var(--phx-sidebar-padding, ${e(2)})`,"& > :first-child":{marginBlockStart:"auto"}})),f=t("div",{name:p,slot:"userMessage"})(({theme:{palette:e,spacing:i}})=>({alignSelf:"flex-end",background:`hsl(from ${e.primary.main} h s l / 12%)`,borderRadius:"14px 14px 4px 14px",maxInlineSize:"85%",overflowWrap:"anywhere",paddingBlock:i(1),paddingInline:i(1.5),whiteSpace:"pre-wrap"})),k=t("div",{name:p,slot:"greeting"})({}),v=t("div",{name:p,slot:"status"})(({theme:{palette:e,typography:i}})=>({...i.caption,color:e.text.secondary})),w=t("div",{name:p,slot:"notice"})(({theme:{palette:e,shape:i,spacing:a}})=>({alignItems:"center",background:e.action.hover,borderRadius:i.borderRadius,display:"flex",flexWrap:"wrap",gap:a(1),justifyContent:"space-between",paddingBlock:a(1),paddingInline:a(1.5)})),b=t(s,{name:p,slot:"newReplyButton"})(({theme:{palette:e,spacing:i}})=>({background:e.text.primary,borderRadius:"calc(infinity * 1px)",color:e.background.paper,insetBlockEnd:i(1),insetInlineStart:"50%",position:"absolute",translate:"-50% 0","&:hover":{background:e.text.secondary}})),x=({item:i,isActive:a})=>r(i).with({kind:"user"},({text:i})=>e(f,{children:i})).with({kind:"text"},({markdown:i,isStreaming:a})=>e(d,{markdown:i,isStreaming:a})).with({kind:"tools"},({tools:i})=>e(l,{tools:i,isActive:a})).exhaustive();export const AssistantThread=({chat:t,onRetry:l})=>{let d=t?.messages,p=t?.isLoading??!1,{items:f,isThinking:y}=n(()=>h(d??[],p),[d,p]),j=n(()=>f.filter(({kind:e})=>"user"===e).length,[f]),I=t?.error?m(t.failedStatus):void 0,{scrollerRef:S,hasUnseenContent:T,handleScroll:A,scrollToEnd:B}=c(n(()=>({messages:d,isThinking:y,failure:I?.kind}),[d,y,I?.kind]),j);return i(g,{children:[i(u,{ref:S,onScroll:A,role:"log","aria-live":"polite",children:[t?.isLoaded===!0&&0===f.length&&!p&&!I&&e(k,{children:e(a,{id:"sdk.assistant.thread.greeting",message:"Hi. I can answer questions about this design and change it with you. Tell me what you need."})}),f.map(i=>e(x,{item:i,isActive:p&&i===f.at(-1)},i.id)),y&&e(v,{children:e(a,{id:"sdk.assistant.thread.thinking",message:"Thinking…"})}),I&&i(w,{role:"alert",children:[e("span",{children:r(I.kind).with("notAvailable",()=>e(a,{id:"sdk.assistant.failure.notAvailable",message:"The assistant is not available here."})).with("busy",()=>e(a,{id:"sdk.assistant.failure.busy",message:"The assistant is still working on an earlier message."})).with("unavailable",()=>e(a,{id:"sdk.assistant.failure.unavailable",message:"The assistant is unavailable right now."})).exhaustive()}),I.canRetry&&e(s,{size:"small",variant:"outlined",color:"inherit",onClick:l,children:e(a,{id:"sdk.assistant.failure.retry.label",message:"Try again"})})]})]}),T&&e(b,{size:"small",onClick:B,endIcon:e(o,{name:"CaretDown"}),children:e(a,{id:"sdk.assistant.thread.newReply.label",message:"New reply"})})]})};
@@ -0,0 +1,18 @@
1
+ /**
2
+ * What the panel tells the user when the assistant failed:
3
+ * - `notAvailable`: the server refuses this configuration or customer, and asking again will not help
4
+ * - `busy`: the server is still answering an earlier message of this thread
5
+ * - `unavailable`: anything else, including a dropped connection and a reply that broke off
6
+ */
7
+ export type AssistantFailureKind = 'notAvailable' | 'busy' | 'unavailable';
8
+ export interface AssistantFailure {
9
+ readonly kind: AssistantFailureKind;
10
+ /** True when loading the thread again may help, so the panel offers "Try again". */
11
+ readonly canRetry: boolean;
12
+ }
13
+ /**
14
+ * Maps a failed assistant request to what the panel shows, by the HTTP status the server answered
15
+ * with. A failure without a status never reached the server or broke off mid-reply; it gets the same
16
+ * generic message as an unknown status, because the chat client does not tell those apart.
17
+ */
18
+ export declare const describeAssistantFailure: (failedStatus: number | undefined) => AssistantFailure;
@@ -0,0 +1 @@
1
+ export const describeAssistantFailure=a=>403===a?{kind:"notAvailable",canRetry:!1}:409===a?{kind:"busy",canRetry:!0}:{kind:"unavailable",canRetry:!0};
@@ -0,0 +1,2 @@
1
+ export { AssistantPanel, ASSISTANT_PANEL_SLOT } from './AssistantPanel.js';
2
+ export { AssistantPanelProvider, useAssistantPanel, type AssistantPanelState } from './AssistantPanelProvider.js';
@@ -0,0 +1 @@
1
+ export{AssistantPanel,ASSISTANT_PANEL_SLOT}from"./AssistantPanel.js";export{AssistantPanelProvider,useAssistantPanel}from"./AssistantPanelProvider.js";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Makes the Markdown of a reply that is still streaming renderable as it stands, so half-written
3
+ * markup does not flash as raw characters: an open code fence is closed, an inline code span, a link
4
+ * or a table header the model has not finished is held back, and open bold text is closed.
5
+ */
6
+ export declare const completeStreamedMarkdown: (text: string) => string;
@@ -0,0 +1,2 @@
1
+ let e=(e,t)=>e.split(t).length-1,t=/^\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/;export const completeStreamedMarkdown=l=>{if(e(l,"```")%2==1)return`${l}
2
+ \`\`\``;let n=l.lastIndexOf("```"),s=-1===n?"":l.slice(0,n+3),r=l.slice(s.length),i=(e=>{let l=e.split("\n"),n=l.length;for(;n>0&&l[n-1]?.trimStart().startsWith("|");)n-=1;let s=l.slice(n),r=s[1],i=null!=r&&s.length>2&&t.test(r);return 0===s.length||i?e:l.slice(0,n).join("\n")})((e(r,"`")%2==1?r.slice(0,r.lastIndexOf("`")):r).replace(/!?\[([^\]\n]*)\]\([^)\n]*$/,"$1").replace(/!?\[[^\]\n]*$/,"")),c=e(i,"**")%2==1?`${i}**`:i;return`${s}${c}`};
@@ -0,0 +1,2 @@
1
+ /** Theme component name of the built-in assistant panel; its slots are listed in the ui-sdk theme types. */
2
+ export declare const AssistantPanelName: 'PhxAssistantPanel';
@@ -0,0 +1 @@
1
+ export const AssistantPanelName="PhxAssistantPanel";
@@ -0,0 +1,37 @@
1
+ import type { UIMessage } from '@tanstack/ai-react';
2
+ import { type AssistantToolChip } from './tool-chip.js';
3
+ /** A tool call of the thread, with what its chip shows and what a click on the chip logs. */
4
+ export interface AssistantToolItem {
5
+ readonly id: string;
6
+ readonly chip: AssistantToolChip;
7
+ readonly input: unknown;
8
+ readonly result: unknown;
9
+ }
10
+ /**
11
+ * One block of the thread, in reading order: a user bubble, a stretch of assistant Markdown, or a
12
+ * row of chips for the tool calls that follow each other without text in between.
13
+ */
14
+ export type AssistantThreadItem = {
15
+ readonly kind: 'user';
16
+ readonly id: string;
17
+ readonly text: string;
18
+ } | {
19
+ readonly kind: 'text';
20
+ readonly id: string;
21
+ readonly markdown: string;
22
+ readonly isStreaming: boolean;
23
+ } | {
24
+ readonly kind: 'tools';
25
+ readonly id: string;
26
+ readonly tools: readonly AssistantToolItem[];
27
+ };
28
+ export interface AssistantThreadView {
29
+ readonly items: readonly AssistantThreadItem[];
30
+ /** True while a reply runs and neither streaming text nor a row of tool chips shows that it does. */
31
+ readonly isThinking: boolean;
32
+ }
33
+ /**
34
+ * Lays the messages of the chat out as the blocks the thread renders. Reasoning is left out; tool
35
+ * results are not shown on their own but decide the state of the chip of their call.
36
+ */
37
+ export declare const buildThreadView: (messages: ReadonlyArray<UIMessage>, isReplying: boolean) => AssistantThreadView;
@@ -0,0 +1 @@
1
+ import{describeToolCall as t}from"./tool-chip.js";let e=t=>{try{return JSON.parse(t)}catch{return t}},r=t=>t.map(t=>"text"===t.type?t.content:"").join("").trim();export const buildThreadView=(o,n)=>{let l=(t=>{let r=new Map;for(let o of t)for(let t of o.parts)"tool-result"===t.type&&r.set(t.toolCallId,{result:"string"==typeof t.content?e(t.content):t.content,error:"error"===t.state?t.error??"The tool call failed.":void 0});return r})(o),i=[],s=o.at(-1),a=t=>{let e=i.at(-1);e?.kind==="tools"?i[i.length-1]={...e,tools:[...e.tools,t]}:i.push({kind:"tools",id:t.id,tools:[t]})};for(let e of o){if("user"===e.role){let t=r(e.parts);t&&i.push({kind:"user",id:e.id,text:t});continue}let o=n&&e===s;e.parts.forEach((r,n)=>{let s=`${e.id}:${n}`;if("text"===r.type){if(r.content.trim()){let t=o&&n===e.parts.length-1;i.push({kind:"text",id:s,markdown:r.content,isStreaming:t})}return}if("tool-call"!==r.type)return;let p=l.get(r.id),d=r.input,u=r.output??p?.result;a({id:r.id,input:d,result:u,chip:t({name:r.name,input:d,result:u,error:p?.error,isReplyRunning:o})})})}let p=i.at(-1),d=s?.role==="assistant"&&(p?.kind==="text"&&p.isStreaming||p?.kind==="tools");return{items:i,isThinking:n&&!d}};
@@ -0,0 +1,37 @@
1
+ /**
2
+ * How a tool call of the assistant looks in the thread: still running, finished, a change that was
3
+ * not kept (`failed`), or a read that answered with errors (`flawed`), which matters less because
4
+ * the assistant usually just asks again.
5
+ */
6
+ export type AssistantToolChipState = 'running' | 'done' | 'failed' | 'flawed';
7
+ /** What a tool chip is called: the operation names the model gave, a fixed text, or the tool's name. */
8
+ export type AssistantToolChipLabel = {
9
+ readonly kind: 'text';
10
+ readonly text: string;
11
+ } | {
12
+ readonly kind: 'updateDesign';
13
+ } | {
14
+ readonly kind: 'readDesign';
15
+ };
16
+ export interface AssistantToolChip {
17
+ readonly label: AssistantToolChipLabel;
18
+ readonly state: AssistantToolChipState;
19
+ }
20
+ /** A tool call of a message as the chip reads it. */
21
+ export interface AssistantToolCall {
22
+ readonly name: string;
23
+ /** The parsed input of the call; nothing while the model is still writing it. */
24
+ readonly input: unknown;
25
+ /** The result of the call, parsed from JSON; nothing while there is none. */
26
+ readonly result: unknown;
27
+ /** The error text of a call the chat client could not run. */
28
+ readonly error: string | undefined;
29
+ /** True while the reply this call belongs to is still running. */
30
+ readonly isReplyRunning: boolean;
31
+ }
32
+ /**
33
+ * Describes one tool call for its chip: the label in the order operation names, fixed text for the
34
+ * `apply` and `query` tools, tool name; and whether the call runs, finished, or went wrong. Why it
35
+ * went wrong is not part of it: the tool result words that for the model, not for the user.
36
+ */
37
+ export declare const describeToolCall: (call: AssistantToolCall) => AssistantToolChip;
@@ -0,0 +1 @@
1
+ import{Kind as e,parse as t}from"graphql";import{z as n}from"zod";import{applyTool as r,queryTool as a}from"@salsita-ai/contracts/assistant";let o=n.object({operations:n.array(n.object({query:n.string(),operationName:n.string().nullish()}))}),i=n.object({results:n.array(n.object({errors:n.array(n.unknown()).optional()})),committed:n.boolean()});export const describeToolCall=n=>{let s=(({name:n,input:i})=>{let s,l=(s=o.safeParse(i)).success?s.data.operations.flatMap(({query:n,operationName:r})=>r??(n=>{try{let r=t(n).definitions.find(t=>t.kind===e.OPERATION_DEFINITION);return r?.kind===e.OPERATION_DEFINITION?r.name?.value:void 0}catch{return}})(n)??[]):[];return l.length>0?{kind:"text",text:l.join(", ")}:n===r.name?{kind:"updateDesign"}:n===a.name?{kind:"readDesign"}:{kind:"text",text:n}})(n);return null==n.result&&null==n.error?{label:s,state:n.isReplyRunning?"running":"done"}:{label:s,state:(({name:e,result:t,error:n})=>{if(null!=n)return e===a.name?"flawed":"failed";let o=i.safeParse(t);if(!o.success)return"done";let{results:s,committed:l}=o.data;return e===r.name?l?"done":"failed":s.some(({errors:e})=>(e?.length??0)>0)||0===s.length?"flawed":"done"})(n)}};
@@ -0,0 +1,17 @@
1
+ import { type RefObject } from 'react';
2
+ export interface FollowStream<ELEMENT extends HTMLElement> {
3
+ readonly scrollerRef: RefObject<ELEMENT>;
4
+ /** True when content arrived while the user was reading further up. */
5
+ readonly hasUnseenContent: boolean;
6
+ readonly handleScroll: () => void;
7
+ readonly scrollToEnd: () => void;
8
+ }
9
+ /**
10
+ * Keeps a scrolling thread at its end while content arrives, until the user scrolls up; from then on
11
+ * it only reports that there is unseen content, and follows again once the user is back at the end.
12
+ *
13
+ * @param content changes whenever the thread's content does
14
+ * @param followKey changes when the thread must return to its end whatever the user did, such as
15
+ * when the user sends a message
16
+ */
17
+ export declare const useFollowStream: <ELEMENT extends HTMLElement>(content: unknown, followKey: unknown) => FollowStream<ELEMENT>;
@@ -0,0 +1 @@
1
+ import{useCallback as r,useEffect as e,useLayoutEffect as t,useRef as l,useState as c}from"react";export const useFollowStream=(o,n)=>{let s=l(null),u=l(!0),[i,g]=c(!1),h=r(()=>{let r=s.current;u.current=!0,g(!1),r&&(r.scrollTop=r.scrollHeight)},[]),H=r(()=>{let r=s.current;r&&(u.current=r.scrollHeight-r.scrollTop-r.clientHeight<=24,u.current&&g(!1))},[]);return t(()=>{h()},[n,h]),t(()=>{let r=s.current;r&&(u.current?r.scrollTop=r.scrollHeight:r.scrollHeight>r.clientHeight&&g(!0))},[o]),e(()=>{let r=s.current;if(!r)return;let e=new ResizeObserver(()=>{u.current&&(r.scrollTop=r.scrollHeight)});return e.observe(r),()=>{e.disconnect()}},[]),{scrollerRef:s,hasUnseenContent:i,handleScroll:H,scrollToEnd:h}};