{"id":32145,"date":"2025-12-17T13:58:54","date_gmt":"2025-12-17T12:58:54","guid":{"rendered":"https:\/\/www.angulararchitects.io\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/"},"modified":"2025-12-17T18:40:27","modified_gmt":"2025-12-17T17:40:27","slug":"generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown","status":"publish","type":"post","link":"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/","title":{"rendered":"Generative UI for AI Assistants: Component Control with Hashbrown"},"content":{"rendered":"<div class=\"wp-post-series-box series-ai-assistants wp-post-series-box--expandable\">\n\t\t\t<input id=\"collapsible-series-ai-assistants6a82d0015571e\" class=\"wp-post-series-box__toggle_checkbox\" type=\"checkbox\">\n\t\n\t<label\n\t\tclass=\"wp-post-series-box__label\"\n\t\t\t\t\tfor=\"collapsible-series-ai-assistants6a82d0015571e\"\n\t\t\ttabindex=\"0\"\n\t\t\t\t>\n\t\t<p class=\"wp-post-series-box__name wp-post-series-name\">\n\t\t\tThis is post 2 of 3 in the series <em>&ldquo;AI Assistants&rdquo;<\/em>\t\t<\/p>\n\t\t\t<\/label>\n\n\t\t\t<div class=\"wp-post-series-box__posts\">\n\t\t\t<ol>\n\t\t\t\t\t\t\t\t\t<li><a href=\"https:\/\/www.angulararchitects.io\/en\/blog\/an-ai-assistant-for-your-angular-application-tool-calling-in-the-frontend-with-hashbrown\/\">An AI Assistant for your Angular Application: Tool Calling in the Frontend with Hashbrown<\/a><\/li>\n\t\t\t\t\t\t\t\t\t<li><span class=\"wp-post-series-box__current\">Generative UI for AI Assistants: Component Control with Hashbrown<\/span><\/li>\n\t\t\t\t\t\t\t\t\t<li><a href=\"https:\/\/www.angulararchitects.io\/en\/blog\/dynamic-reports-natural-lanaguage-queries-with-on-the-fly-code-generation\/\">Dynamic Reports: Natural Language Queries with On-the-Fly Code Generation<\/a><\/li>\n\t\t\t\t\t\t\t<\/ol>\n\t\t<\/div>\n\t<\/div>\n<p>In my last article about Hashbrown, I showed how an LLM-based chat assistant can be integrated into an Angular application. Using tool calling, the language model can invoke functions that provide data or trigger actions in the frontend.<\/p>\n<p>This article goes a step further: The LLM now selects one or more UI components and displays them directly in the chat. Responses are therefore no longer limited to text, but are enhanced with visual and interactive elements.<\/p>\n<p>\ud83d\udcc2 <a href=\"https:\/\/github.com\/manfredsteyer\/modern\/tree\/hashbrown_ui\">Source Code<\/a> (\ud83d\udd00 branch <code>hashbrown_ui<\/code>)<\/p>\n<h2>Demo Application<\/h2>\n<p>To demonstrate how a language model answer with components, an extended version of the solution from the previous article is used. It presents flight components directly within the chat history:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/example02-scaled.png\" alt=\"The chat responds with components\" \/><\/p>\n<p>The LLM can also choose to include multiple components in a single response:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/example02-scaled.png\" alt=\"The same component can also be used multiple times in an answer\" \/><\/p>\n<h2>UI chat with uiChatResource<\/h2>\n<p>The <em>uiChatResource<\/em> provided by Hashbrown supports not only tool calling but also answering questions using components. It can be used as a replacement for the <em>chatResource<\/em> in the previous article:<\/p>\n<pre><code class=\"language-ts\">@Component([\u2026])\nexport class AssistantChatComponent {\n\n  message = signal(&#039;&#039;);\n\n  chat = uiChatResource({\n    model: &#039;gpt-5-chat-latest&#039;,\n    system: `\n      You are an UI assistent that helps with finding flights [\u2026]\n    `,\n    tools: [\n      findFlightsTool,\n      getLoadedFlights,\n      getBookedFlights,\n      [\u2026]\n    ],\n    components: [\n      flightWidget, \n      messageWidget\n    ],\n  });\n\n  [\u2026]\n\n  submit() {\n    const message = this.message();\n    this.message.set(&#039;&#039;);\n    this.chat.sendMessage({ role: &#039;user&#039;, content: message });\n  }\n}<\/code><\/pre>\n<p>The components provided to the LLM must be registered under <code>components\\<\/code>. These are Angular components that -- similar to tools -- are described using the Skillet schema language. The next section will discuss this in more detail.<\/p>\n<p>It is important to note that when using the <em>uiChatResource<\/em>, the LLM answers each question with one or more components. Therefore, this example also registers a <em>messageWidget<\/em>, which simply receives and displays a string.<\/p>\n<p>To present the components selected by the LLM in the chat history, the component <code>hb-render-message<\/code> provided by Hashbrown is used:<\/p>\n<pre><code class=\"language-html\">@for (message of messageModels(); track $index) {\n&lt;article class=&quot;msg assistant&quot;&gt;\n  &lt;div class=&quot;avatar&quot;&gt;{{ message.icon }}&lt;\/div&gt;\n  &lt;div&gt;\n    &lt;div class=&quot;bubble&quot;&gt;\n      @if (message.role === &#039;assistant&#039;) {\n        &lt;hb-render-message [message]=&quot;message&quot; \/&gt;\n      }\n      @else {\n        &lt;app-message [data]=&quot;message.contentString&quot;&gt;&lt;\/app-message&gt;\n      }\n\n      @for(toolCall of message.toolCalls; track toolCall.toolCallId) {\n         [\u2026]\n      }\n    &lt;\/div&gt;\n  &lt;\/div&gt;\n&lt;\/article&gt;\n}<\/code><\/pre>\n<p>This component is only necessary for responses from the LLM. Such responses are indicated by the role <code>assistant<\/code>. The user's requests in this example are purely textual. They are located in the <code>content<\/code> property.<\/p>\n<p>As <code>content<\/code> also allows other types like <code>number<\/code> or <code>JSON<\/code>, it must be converted to a string before <code>app-message<\/code> can display it. This and similar tasks, such as selecting an icon for the respective role (<code>assistant<\/code> , <code>user<\/code>), are handled by a projection using <code>computed<\/code>.<\/p>\n<pre><code class=\"language-ts\">messageModels = computed(() =&gt;\n  this.chat.value().map((message) =&gt; ({\n    ...message,\n    contentString: String(message.content),\n    icon: this.icons[message.role] || &#039;\u2753&#039;,\n    toolCalls: message.role === &#039;assistant&#039; ? \n      message.toolCalls : [],\n  }))\n);<\/code><\/pre>\n<h2>Components for chat: Dumb Components with Smart Wrappers<\/h2>\n<p>The components offered to the LLM are either Dumb Components or, as in the case of the <code>flightWidget<\/code>, a smart wrapper around a Dumb Component:<\/p>\n<pre><code class=\"language-ts\">@Component({\n  selector: &#039;app-flight-widget&#039;,\n  imports: [FlightCardComponent],\n  template: `\n    &lt;div class=&quot;flight&quot;&gt;\n      &lt;app-flight-card [item]=&quot;flight()&quot; [selected]=&quot;isSelected()&quot;&gt;\n        &lt;div&gt;\n          @if(isBooked()) {\n          &lt;button class=&quot;btn btn-default&quot; (click)=&quot;checkIn()&quot;&gt;Check in&lt;\/button&gt;\n          } @else if (isSelected()){\n          &lt;button class=&quot;btn btn-default&quot; (click)=&quot;select(false)&quot;&gt;\n            Remove\n          &lt;\/button&gt;\n          } @else {\n          &lt;button class=&quot;btn btn-default&quot; (click)=&quot;select(true)&quot;&gt;Select&lt;\/button&gt;\n          }\n        &lt;\/div&gt;\n      &lt;\/app-flight-card&gt;\n    &lt;\/div&gt;\n  `,\n  styles: `\n    .flight {\n      margin: 20px 0;\n    }\n  `,\n})\nexport class FlightWidgetComponent {\n  router = inject(Router);\n  store = inject(FlightBookingStore);\n\n  flight = input.required&lt;Flight&gt;();\n  status = input&lt;&#039;booked&#039; | &#039;other&#039;&gt;(&#039;other&#039;);\n\n  isBooked = computed(() =&gt; this.status() === &#039;booked&#039;);\n  isSelected = computed(() =&gt; this.store.basket()[this.flight().id]);\n\n  checkIn(): void {\n    this.router.navigate([&#039;\/checkin&#039;, this.flight().id]);\n  }\n\n  select(selected: boolean): void {\n    this.store.updateBasket(this.flight().id, selected);\n  }\n}<\/code><\/pre>\n<p>This wrapper delegates its inputs to the wrapped dumb component and handles its events. In the latter case, it triggers actions in the stores of the individual features or initiates route changes.<\/p>\n<p>The key aspect here is the input <code>status<\/code>, which indicates whether a given flight is already booked or found through a flight search. The LLM system must derive this value from the conversation history. As we will see at the end of the article, smaller, cost-effective models like Gemini Flash require some assistance with this task.<\/p>\n<p>Based on the value of <code>status<\/code>, the widget selects a button for the flight map: Booked flights get a  <em>Check in<\/em> button, and flights found during the flight search get a button to add the flight to the shopping cart or remove it again.<\/p>\n<h2>Describing Components<\/h2>\n<p>Hashbrown's <code>exposeComponent<\/code> function describes the provided components:<\/p>\n<pre><code class=\"language-ts\">import { exposeComponent } from &#039;@hashbrownai\/angular&#039;;\nimport { s } from &#039;@hashbrownai\/core&#039;;\n[\u2026]\n\nexport const flightWidget = exposeComponent(FlightWidgetComponent, {\n  name: &#039;flightWidget&#039;,\n  description: `\n    Displays a flight or flight ticket. Use this instead of textual \n    descriptions of flights or tickets.\n    `,\n  input: {\n    flight: FlightSchema,\n    status: s.enumeration(\n      <code>Whether the flight is booked or not [\u2026]<\/code>,\n      [&#039;booked&#039;, &#039;other&#039;]\n    ),\n  },\n});<\/code><\/pre>\n<p>The LLM uses the stored textual description to decide when to use the component. The individual inputs also need to be described. For this, the Hashbrown's Skillet schema language is leveraged. The example sets up the <code>status<\/code> input as an enumeration with two possible values. To describe the <code>flight<\/code> input, it delegates to an existing schema:<\/p>\n<pre><code class=\"language-ts\">import { s } from &quot;@hashbrownai\/core&quot;;\n\nexport const FlightSchema = s.object(&#039;Flight to be displayed&#039;, {\n  id: s.number(&#039;The flight id&#039;),\n  from: s.string(&#039;Departure city. No code but the city name&#039;),\n  to: s.string(&#039;Arrival city. No code but the city name&#039;),\n  date: s.string(&#039;Departure date in ISO format&#039;),\n  delay: s.number(&#039;If delayed, this represents the delay in minutes&#039;),\n});<\/code><\/pre>\n<h2>Further Details &amp; Practical Application<\/h2>\n<p>If you want to go beyond individual examples and look at these topics in a<br \/>\nstructured, end-to-end way, we cover them in depth in the Angular Architecture Workshop.<\/p>\n<p>The focus is on real-world architecture decisions \u2013 including how and where AI<br \/>\nfits into enterprise Angular applications.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/sujet-en.jpg\" alt=\"\" \/><\/p>\n<p><a href=\"https:\/\/www.angulararchitects.io\/en\/angular-workshops\/advanced-angular-enterprise-architecture-incl-ivy\/\">English Version<\/a> | <a href=\"https:\/\/www.angulararchitects.io\/schulungen\/advanced-angular-enterprise-anwendungen-und-architektur\">German Version<\/a><\/p>\n<h2>Under the Hood: Structured Output<\/h2>\n<p>To determine which components should be displayed in the chat history, Hashbrown instructs the LLM to respond only with JSON documents. Such responses are also known as structured output.<\/p>\n<p>These JSON documents received by the model are stored as a string in the <code>content<\/code> property and contain the components to be displayed along with the input values to be passed:<\/p>\n<pre><code class=\"language-json\">[\u2026]\n{\n    &quot;role&quot;: &quot;assistant&quot;,\n    &quot;content&quot;: &quot;{\\&quot;ui\\&quot;:[{\\&quot;messageWidget\\&quot;:{\\&quot;$props\\&quot;:{\\&quot;data\\&quot;:\\&quot;Yes, you have already booked a flight to France.\\&quot;}}},{\\&quot;flightWidget\\&quot;:{\\&quot;$props\\&quot;:{\\&quot;flightInfo\\&quot;:{\\&quot;id\\&quot;:2,\\&quot;from\\&quot;:\\&quot;London\\&quot;,\\&quot;to\\&quot;:\\&quot;Paris\\&quot;,\\&quot;date\\&quot;:\\&quot;2025-12-05T21:15:10.716Z\\&quot;,\\&quot;delay\\&quot;:0,\\&quot;status\\&quot;:\\&quot;booked\\&quot;,\\&quot;delayInfo\\&quot;:\\&quot;delayed\\&quot;}}}}]}&quot;,\n    &quot;toolCalls&quot;: []\n},\n[\u2026]<\/code><\/pre>\n<p>Hashbrown passes the possible components, along with their inputs and descriptions, in a separate section of each request. Technically, this is a JSON schema derived from Skillet.<\/p>\n<h2>Supporting Different Models<\/h2>\n<p>Hashbrown uses JSON-based responses (structured output) to select the desired components. However, some models, such as Google Gemini, do not (yet) support the combination of structured output and tool calling. This problem can be solved when bootstrapping the application using the <code>emulateStructuredOutput<\/code> property:<\/p>\n<pre><code class=\"language-ts\">bootstrapApplication(AppComponent, {\n  providers: [\n    [\u2026]\n    provideHashbrown({\n      baseUrl: &#039;http:\/\/localhost:3000\/api\/chat&#039;,\n      emulateStructuredOutput: true,\n    }),\n  ],\n});<\/code><\/pre>\n<p>If the application sets this property to <code>true<\/code>, Hashbrown defines a pseudo-tool that allows the LLM to select one or more of the offered components. Hashbrown also instructs the language model to respond with a call to this tool.<\/p>\n<h2>Weaker Models: Few Shot Prompting to Help get Things Moving<\/h2>\n<p>To ensure the model always responds with some free text and optionally with one or more components, the rules in the resource's system prompt must be extended. To also help less powerful, inexpensive models like Gemini Flash, this prompt is further enhanced with a few examples:<\/p>\n<pre><code class=\"language-text\">[\u2026]\n\n## Rules:\n[\u2026]\n- Answer questions with the messageWidget to provide some text to the user. \n- When appropriate, *also* answer with other components (widgets), e.g., \n      the flightWidget to display information about a flight or a ticket\n- Instead of describing a flight, use the flightWidget\n- Don&#039;t call the same tool more than once with the same parameters!\n\n## EXAMPLE\n\n- User: Which flights did I book?\n- Assistant:\n  - UI: messageWidget(You&#039;ve booked these 3 flights)\n  - UI: flightWidget({id: 0, from: &#039;...&#039;, to:&#039;...&#039;, ...})\n\n## NEGATIVE EXAMPLE\n\nDon&#039;t call the same tool several times in a row with the same parameters:\n\n- User: Search for flights from A to B\n- Assistant:\n  - Tool: findFlights({from: &#039;A&#039;, to: &#039;B&#039;})\n  - Tool: findFlights({from: &#039;A&#039;, to: &#039;B&#039;})\n  - Tool: findFlights({from: &#039;A&#039;, to: &#039;B&#039;})\n\n[\u2026]<\/code><\/pre>\n<p>Placing such examples in the prompt has been proven to improve the quality of model responses. When multiple examples are placed, it is referred to as <em>few-shot prompting<\/em>; when only a single example is provided, the term <em>one-shot prompting<\/em> is used.<\/p>\n<p>The same technique is necessary so that weaker models can correctly derive the flight status (<code>booked<\/code>, <code>other<\/code>) from the conversation history:<\/p>\n<pre><code class=\"language-ts\">export const flightWidget = exposeComponent(FlightWidgetComponent, {\n  name: &#039;flightWidget&#039;,\n  description: <code>[\u2026]<\/code>,\n  input: {\n    flight: FlightSchema,\n    status: s.enumeration(\n      `Whether the flight is booked or not. \n\n      A flight has the status &#039;booked&#039; **only** when retrieved \n      via the tool &#039;getBookedFlights&#039;.\n\n      ## Example for infering a status &#039;booked&#039;\n      - User: Which flights did I book?\n      - Assistant:\n          - Tool: getBookedFlights()\n          - UI: flightWidget({flightInfo: { id: 0, ..., status: &#039;booked&#039; }})\n\n      ## Example for infering a status &#039;other&#039;\n      - User: Which of the found flights is the earliest one?\n      - Assistant:\n          - Tool: getLoadedFlights()\n          - UI: flightWidget({flightInfo: { id: 0, ..., status: &#039;other&#039; }})\n      `,\n      [&#039;booked&#039;, &#039;other&#039;]\n    ),\n  },\n});<\/code><\/pre>\n<p>Examples written in prose -- like the ones shown -- are easy to write and read. However, prose is also prone to errors, as such examples can use components and parameters that no longer exist. The <em>prompt<\/em> function provided by Hashbrown for tagging strings offers a solution:<\/p>\n<pre><code class=\"language-ts\">uiChatResource({\n  system: prompt`\n    [...]\n    &lt;user&gt;Hello&lt;\/user&gt;\n    &lt;assistant&gt;\n      &lt;ui&gt;\n        &lt;app-message \n            data=&quot;How may I assist you?&quot; \/&gt;\n      &lt;\/ui&gt;\n    &lt;\/assistant&gt;\n  `,\n  components: [\n    exposeComponent(MessageComponent, { ... })\n  ]\n});<\/code><\/pre>\n<p>The tag function <code>prompt<\/code> checks whether these examples, which must be in the displayed XML dialect, correlate with the registered components. Currently, this function can only be used for the system prompt in the resource. However, since our example overrides the system prompt server-side for security reasons, and examples are also necessary within the component descriptions, it is limited to prose examples.<\/p>\n<h2>Conclusion<\/h2>\n<p>LLMs can be used to control interactive UI components via Structured Output, provided these components and their inputs are described and registered in a structured manner. Combined with Hashbrown and Angular, this creates AI-powered user interfaces that go far beyond traditional chat interfaces.<\/p>\n<p>At the same time, it becomes clear that smaller models need to be supported by targeted prompting and examples to deliver consistent results. Few-shot prompting and clearly described components form the technical basis for this.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is post 2 of 3 in the series &ldquo;AI Assistants&rdquo; An AI Assistant for your Angular Application: Tool Calling in the Frontend with Hashbrown Generative UI for AI Assistants: Component Control with Hashbrown Dynamic Reports: Natural Language Queries with On-the-Fly Code Generation In my last article about Hashbrown, I showed how an LLM-based chat [&hellip;]<\/p>\n","protected":false},"author":25,"featured_media":32144,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_price":"","_stock":"","_tribe_ticket_header":"","_tribe_default_ticket_provider":"","_ticket_start_date":"","_ticket_end_date":"","_tribe_ticket_show_description":"","_tribe_ticket_show_not_going":false,"_tribe_ticket_use_global_stock":"","_tribe_ticket_global_stock_level":"","_global_stock_mode":"","_global_stock_cap":"","_tribe_rsvp_for_event":"","_tribe_ticket_going_count":"","_tribe_ticket_not_going_count":"","_tribe_tickets_list":"[]","_tribe_ticket_has_attendee_info_fields":false,"footnotes":""},"categories":[18],"tags":[],"class_list":["post-32145","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","post_series-ai-assistants"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Generative UI for AI Assistants: Component Control with Hashbrown - ANGULARarchitects<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Generative UI for AI Assistants: Component Control with Hashbrown - ANGULARarchitects\" \/>\n<meta property=\"og:description\" content=\"This is post 2 of 3 in the series &ldquo;AI Assistants&rdquo; An AI Assistant for your Angular Application: Tool Calling in the Frontend with Hashbrown Generative UI for AI Assistants: Component Control with Hashbrown Dynamic Reports: Natural Language Queries with On-the-Fly Code Generation In my last article about Hashbrown, I showed how an LLM-based chat [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/\" \/>\n<meta property=\"og:site_name\" content=\"ANGULARarchitects\" \/>\n<meta property=\"article:published_time\" content=\"2025-12-17T12:58:54+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-12-17T17:40:27+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/sujet-1024x536.png\" \/>\n<meta name=\"author\" content=\"Manfred Steyer\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/sujet-1024x536.png\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Manfred Steyer\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/\"},\"author\":{\"name\":\"Manfred Steyer\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a\"},\"headline\":\"Generative UI for AI Assistants: Component Control with Hashbrown\",\"datePublished\":\"2025-12-17T12:58:54+00:00\",\"dateModified\":\"2025-12-17T17:40:27+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/\"},\"wordCount\":1084,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_1099422191-1.jpg\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/\",\"name\":\"Generative UI for AI Assistants: Component Control with Hashbrown - ANGULARarchitects\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_1099422191-1.jpg\",\"datePublished\":\"2025-12-17T12:58:54+00:00\",\"dateModified\":\"2025-12-17T17:40:27+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#primaryimage\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_1099422191-1.jpg\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_1099422191-1.jpg\",\"width\":1000,\"height\":721},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.angulararchitects.io\/en\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Generative UI for AI Assistants: Component Control with Hashbrown\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"name\":\"ANGULARarchitects\",\"description\":\"AngularArchitects.io\",\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\",\"name\":\"ANGULARarchitects\",\"alternateName\":\"SOFTWAREarchitects\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"width\":644,\"height\":216,\"caption\":\"ANGULARarchitects\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/github.com\/angular-architects\",\"https:\/\/www.linkedin.com\/company\/angular-architects\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a\",\"name\":\"Manfred Steyer\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g\",\"caption\":\"Manfred Steyer\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Generative UI for AI Assistants: Component Control with Hashbrown - ANGULARarchitects","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/","og_locale":"en_US","og_type":"article","og_title":"Generative UI for AI Assistants: Component Control with Hashbrown - ANGULARarchitects","og_description":"This is post 2 of 3 in the series &ldquo;AI Assistants&rdquo; An AI Assistant for your Angular Application: Tool Calling in the Frontend with Hashbrown Generative UI for AI Assistants: Component Control with Hashbrown Dynamic Reports: Natural Language Queries with On-the-Fly Code Generation In my last article about Hashbrown, I showed how an LLM-based chat [&hellip;]","og_url":"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/","og_site_name":"ANGULARarchitects","article_published_time":"2025-12-17T12:58:54+00:00","article_modified_time":"2025-12-17T17:40:27+00:00","og_image":[{"url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/sujet-1024x536.png","type":"","width":"","height":""}],"author":"Manfred Steyer","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/sujet-1024x536.png","twitter_misc":{"Written by":"Manfred Steyer","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#article","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/"},"author":{"name":"Manfred Steyer","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a"},"headline":"Generative UI for AI Assistants: Component Control with Hashbrown","datePublished":"2025-12-17T12:58:54+00:00","dateModified":"2025-12-17T17:40:27+00:00","mainEntityOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/"},"wordCount":1084,"commentCount":0,"publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_1099422191-1.jpg","inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/","url":"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/","name":"Generative UI for AI Assistants: Component Control with Hashbrown - ANGULARarchitects","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#primaryimage"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_1099422191-1.jpg","datePublished":"2025-12-17T12:58:54+00:00","dateModified":"2025-12-17T17:40:27+00:00","breadcrumb":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#primaryimage","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_1099422191-1.jpg","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2025\/12\/shutterstock_1099422191-1.jpg","width":1000,"height":721},{"@type":"BreadcrumbList","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/generative-ui-for-ai-assistants-component-control-and-structured-output-with-hashbrown\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.angulararchitects.io\/en\/"},{"@type":"ListItem","position":2,"name":"Generative UI for AI Assistants: Component Control with Hashbrown"}]},{"@type":"WebSite","@id":"https:\/\/www.angulararchitects.io\/en\/#website","url":"https:\/\/www.angulararchitects.io\/en\/","name":"ANGULARarchitects","description":"AngularArchitects.io","publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.angulararchitects.io\/en\/#organization","name":"ANGULARarchitects","alternateName":"SOFTWAREarchitects","url":"https:\/\/www.angulararchitects.io\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","width":644,"height":216,"caption":"ANGULARarchitects"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/github.com\/angular-architects","https:\/\/www.linkedin.com\/company\/angular-architects\/"]},{"@type":"Person","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a","name":"Manfred Steyer","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g","caption":"Manfred Steyer"}}]}},"_links":{"self":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/32145","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/users\/25"}],"replies":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/comments?post=32145"}],"version-history":[{"count":8,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/32145\/revisions"}],"predecessor-version":[{"id":32162,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/32145\/revisions\/32162"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media\/32144"}],"wp:attachment":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media?parent=32145"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/categories?post=32145"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/tags?post=32145"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}