@ensembleapp/client-sdk 0.0.10 → 0.0.12

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.
package/dist/index.d.ts CHANGED
@@ -5,9 +5,59 @@ import React$1, { ReactNode } from 'react';
5
5
  import z, { AnyZodObject } from 'zod';
6
6
  import { ClassValue } from 'clsx';
7
7
 
8
+ /**
9
+ * Version selector for tools. This should mirror VersionSelector on server-side
10
+ * - 'latest': prefer draft (v0), fallback to last published
11
+ * - 'published': prefer last published, fallback to draft
12
+ * - number: specific version (1+)
13
+ */
14
+ type VersionSelector = 'latest' | 'published' | number;
15
+ /**
16
+ * Configuration for enriching widget data server-side by calling a tool.
17
+ * Uses JEXL expressions to build tool inputs from the widget payload.
18
+ */
19
+ type WidgetEnrichConfig = {
20
+ /** Tool ID to call for enrichment */
21
+ toolId: string;
22
+ /**
23
+ * Tool version selector. Defaults to 'latest'.
24
+ * - 'latest': prefer draft, fallback to published
25
+ * - 'published': prefer published, fallback to draft
26
+ * - number: specific version (1+)
27
+ */
28
+ version?: VersionSelector;
29
+ /**
30
+ * Maps tool input names to JEXL expressions.
31
+ * Expressions are evaluated with $ as the payload root (auto-normalized).
32
+ * Use ${...} syntax for JEXL expressions, or literal values.
33
+ *
34
+ * Examples:
35
+ * - "${vendors|map('id')|join(',')}" → "v1,v2,v3"
36
+ * - "${limit}" → 10
37
+ * - "static value" → "static value"
38
+ *
39
+ * If omitted, the entire payload is passed as tool input.
40
+ */
41
+ inputs?: Record<string, unknown>;
42
+ };
43
+ /** Enrichment result containing the full tool response. TThis should mirror ToolResult on server-side. */
44
+ type EnrichmentResult$1 = {
45
+ success: boolean;
46
+ error?: {
47
+ message: string;
48
+ code?: string;
49
+ };
50
+ data?: unknown;
51
+ metadata?: Record<string, unknown>;
52
+ };
53
+ /** Widget output shape sent to the client */
8
54
  type UIWidget = {
9
55
  widgetType: string;
10
- } & Record<string, unknown>;
56
+ /** LLM-generated data based on the widget schema specified by the client */
57
+ payload: unknown;
58
+ /** Server-injected enrichment result from tool call. This mirror ToolResult on server-side. */
59
+ enrichedResult?: EnrichmentResult$1;
60
+ };
11
61
 
12
62
  type DeprecatedChatConfig = {
13
63
  /** @deprecated use agentId instead */
@@ -125,12 +175,38 @@ declare function useFeedback({ api, threadId, agentId, agentExecutionId, }: UseF
125
175
  error: Error | null;
126
176
  };
127
177
 
128
- interface UIWidgetDefinition<TSchema extends AnyZodObject = AnyZodObject> {
178
+ /** Enrichment result containing the full tool response - this should mirror ToolResult
179
+ * OR maybe we should declare ToolResult here? */
180
+ interface EnrichmentResult<TData = unknown> {
181
+ success: boolean;
182
+ error?: {
183
+ message: string;
184
+ code?: string;
185
+ };
186
+ data?: TData;
187
+ metadata?: Record<string, unknown>;
188
+ }
189
+ /**
190
+ * Props passed to widget render functions.
191
+ * Contains both the validated payload and optional enriched result.
192
+ */
193
+ interface WidgetRenderProps<TPayload, TEnrichedData = unknown> {
194
+ /** The validated widget payload matching the schema */
195
+ payload: TPayload;
196
+ /** Server-injected enrichment result (only present if widget has enrich config) */
197
+ enrichedResult?: EnrichmentResult<TEnrichedData>;
198
+ }
199
+ interface UIWidgetDefinition<TSchema extends AnyZodObject = AnyZodObject, TEnrichedData = unknown> {
129
200
  widgetType: string;
130
201
  schema: TSchema;
131
- render(widget: z.infer<TSchema>): ReactNode;
202
+ /**
203
+ * Optional enrichment config - if provided, server will call a tool to enrich the data.
204
+ * The enriched data is passed to the render function as `enrichedResult.data`.
205
+ */
206
+ enrich?: WidgetEnrichConfig;
207
+ render(props: WidgetRenderProps<z.infer<TSchema>, TEnrichedData>): ReactNode;
132
208
  }
133
- declare const createWidget: <TSchema extends AnyZodObject>({ widgetType, schema, render, }: UIWidgetDefinition<TSchema>) => UIWidgetDefinition<TSchema>;
209
+ declare const createWidget: <TSchema extends AnyZodObject, TEnrichedData = unknown>({ widgetType, schema, enrich, render, }: UIWidgetDefinition<TSchema, TEnrichedData>) => UIWidgetDefinition<TSchema, TEnrichedData>;
134
210
 
135
211
  interface ChatWidgetStyles {
136
212
  primaryColor?: string;
@@ -277,4 +353,4 @@ declare const defaultChatWidgets: UIWidgetDefinition[];
277
353
 
278
354
  declare function cn(...inputs: ClassValue[]): string;
279
355
 
280
- export { type ApiConfig, type ChatContentItem, type ChatMessage, ChatWidget, type ChatWidgetFeedbackOptions, type ChatWidgetInstance, type ChatWidgetConfig as ChatWidgetProps, type ChatWidgetSpeechToTextOptions, type ChatWidgetStyles, type ChatWidgetVoiceOptions, type EmbeddableChatWidgetConfig, type FeedbackRating, type FeedbackState, type MessageFeedback, type MessageSection, type PopupAnchorConfig, PopupChatWidget, type PopupChatWidgetProps, type SubmitFeedbackParams, type TagGroup, TagGroupDisplay, type TagGroupDisplayProps, type ToolCallContent, ToolCallDisplay, type ToolCallDisplayProps, type UIWidgetDefinition, type UseChatConfig, type UseFeedbackConfig, cn, createChatWidget, createWidget, defaultChatWidgets, registerChatWidgets, useChat, useFeedback };
356
+ export { type ApiConfig, type ChatContentItem, type ChatMessage, ChatWidget, type ChatWidgetFeedbackOptions, type ChatWidgetInstance, type ChatWidgetConfig as ChatWidgetProps, type ChatWidgetSpeechToTextOptions, type ChatWidgetStyles, type ChatWidgetVoiceOptions, type EmbeddableChatWidgetConfig, type EnrichmentResult, type FeedbackRating, type FeedbackState, type MessageFeedback, type MessageSection, type PopupAnchorConfig, PopupChatWidget, type PopupChatWidgetProps, type SubmitFeedbackParams, type TagGroup, TagGroupDisplay, type TagGroupDisplayProps, type ToolCallContent, ToolCallDisplay, type ToolCallDisplayProps, type UIWidgetDefinition, type UseChatConfig, type UseFeedbackConfig, type WidgetEnrichConfig, type WidgetRenderProps, cn, createChatWidget, createWidget, defaultChatWidgets, registerChatWidgets, useChat, useFeedback };
package/dist/index.js CHANGED
@@ -23666,13 +23666,14 @@ async function registerChatWidgets({
23666
23666
  threadId,
23667
23667
  widgets
23668
23668
  }) {
23669
- const widgetSchemas = widgets.map(({ widgetType, schema }) => {
23669
+ const widgetSchemas = widgets.map(({ widgetType, schema, enrich }) => {
23670
23670
  schema = schema.extend({
23671
23671
  widgetType: zod_default.literal(widgetType)
23672
23672
  });
23673
23673
  return {
23674
23674
  widgetType,
23675
- schema: toJsonSchema(schema)
23675
+ schema: toJsonSchema(schema),
23676
+ enrich
23676
23677
  };
23677
23678
  });
23678
23679
  const { baseUrl, token, headers: customHeaders = {} } = api;
@@ -24265,7 +24266,8 @@ function MessageItemComponent({
24265
24266
  "]"
24266
24267
  ] }, key);
24267
24268
  }
24268
- return /* @__PURE__ */ jsx5("div", { className: "chat-widget__widget", children: widgetDef.render(item) }, key);
24269
+ const widget = item;
24270
+ return /* @__PURE__ */ jsx5("div", { className: "chat-widget__widget", children: widgetDef.render({ payload: widget.payload, enrichedResult: widget.enrichedResult }) }, key);
24269
24271
  }
24270
24272
  return null;
24271
24273
  };
@@ -24451,6 +24453,8 @@ function ChatWidget({
24451
24453
  const [isMicActive, setIsMicActive] = useState6(false);
24452
24454
  const [isSpeechPending, setIsSpeechPending] = useState6(false);
24453
24455
  const messagesEndRef = useRef2(null);
24456
+ const scrollContainerRef = useRef2(null);
24457
+ const isAtBottomRef = useRef2(true);
24454
24458
  const textareaRef = useRef2(null);
24455
24459
  const suppressScrollRef = useRef2(false);
24456
24460
  const {
@@ -24530,12 +24534,22 @@ function ChatWidget({
24530
24534
  }]);
24531
24535
  }
24532
24536
  }, [introMessage, messages.length, setMessages]);
24537
+ const handleScroll = useCallback4(() => {
24538
+ if (!scrollContainerRef.current) return;
24539
+ const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current;
24540
+ const distanceToBottom = scrollHeight - scrollTop - clientHeight;
24541
+ isAtBottomRef.current = distanceToBottom < 100;
24542
+ }, []);
24533
24543
  useEffect4(() => {
24534
24544
  if (suppressScrollRef.current) {
24535
24545
  suppressScrollRef.current = false;
24536
24546
  return;
24537
24547
  }
24538
- messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
24548
+ const lastMessage = messages[messages.length - 1];
24549
+ const isUserMessage = lastMessage?.role === "user";
24550
+ if (isAtBottomRef.current || isUserMessage) {
24551
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
24552
+ }
24539
24553
  }, [messages, status]);
24540
24554
  useEffect4(() => {
24541
24555
  const timer = setTimeout(() => {
@@ -24611,36 +24625,44 @@ function ChatWidget({
24611
24625
  "data-chat-widget": "",
24612
24626
  children: [
24613
24627
  title && /* @__PURE__ */ jsx5("div", { className: classNames2.header, children: /* @__PURE__ */ jsx5("h3", { className: classNames2.headerTitle, children: title }) }),
24614
- /* @__PURE__ */ jsxs5("div", { className: classNames2.messagesContainer, children: [
24615
- messages.map((message, index) => {
24616
- const isLastMessage = index === messages.length - 1;
24617
- const showFeedback = !(status === "streaming" && isLastMessage);
24618
- const feedbackData = getFeedbackForMessage(message.id);
24619
- return /* @__PURE__ */ jsx5(
24620
- MessageItem,
24621
- {
24622
- message,
24623
- widgets,
24624
- tagExpansionState,
24625
- onTagToggle: handleTagToggle,
24626
- feedbackEnabled,
24627
- showFeedback,
24628
- existingRating: feedbackData?.rating,
24629
- existingComment: feedbackData?.comment,
24630
- feedbackSubmitting: feedbackSubmitting === message.id,
24631
- requireCommentForNegative: feedback?.requireCommentForNegative,
24632
- onFeedbackSubmit: handleFeedbackSubmit
24633
- },
24634
- message.id
24635
- );
24636
- }),
24637
- status === "streaming" && /* @__PURE__ */ jsx5("div", { className: classNames2.loadingContainer, children: /* @__PURE__ */ jsx5("div", { className: classNames2.loadingBubble, children: /* @__PURE__ */ jsxs5("div", { className: classNames2.loadingDots, children: [
24638
- /* @__PURE__ */ jsx5("div", { className: classNames2.loadingDot }),
24639
- /* @__PURE__ */ jsx5("div", { className: classNames2.loadingDot, "data-delay": "1" }),
24640
- /* @__PURE__ */ jsx5("div", { className: classNames2.loadingDot, "data-delay": "2" })
24641
- ] }) }) }),
24642
- /* @__PURE__ */ jsx5("div", { ref: messagesEndRef })
24643
- ] }),
24628
+ /* @__PURE__ */ jsxs5(
24629
+ "div",
24630
+ {
24631
+ className: classNames2.messagesContainer,
24632
+ ref: scrollContainerRef,
24633
+ onScroll: handleScroll,
24634
+ children: [
24635
+ messages.map((message, index) => {
24636
+ const isLastMessage = index === messages.length - 1;
24637
+ const showFeedback = !(status === "streaming" && isLastMessage);
24638
+ const feedbackData = getFeedbackForMessage(message.id);
24639
+ return /* @__PURE__ */ jsx5(
24640
+ MessageItem,
24641
+ {
24642
+ message,
24643
+ widgets,
24644
+ tagExpansionState,
24645
+ onTagToggle: handleTagToggle,
24646
+ feedbackEnabled,
24647
+ showFeedback,
24648
+ existingRating: feedbackData?.rating,
24649
+ existingComment: feedbackData?.comment,
24650
+ feedbackSubmitting: feedbackSubmitting === message.id,
24651
+ requireCommentForNegative: feedback?.requireCommentForNegative,
24652
+ onFeedbackSubmit: handleFeedbackSubmit
24653
+ },
24654
+ message.id
24655
+ );
24656
+ }),
24657
+ status === "streaming" && /* @__PURE__ */ jsx5("div", { className: classNames2.loadingContainer, children: /* @__PURE__ */ jsx5("div", { className: classNames2.loadingBubble, children: /* @__PURE__ */ jsxs5("div", { className: classNames2.loadingDots, children: [
24658
+ /* @__PURE__ */ jsx5("div", { className: classNames2.loadingDot }),
24659
+ /* @__PURE__ */ jsx5("div", { className: classNames2.loadingDot, "data-delay": "1" }),
24660
+ /* @__PURE__ */ jsx5("div", { className: classNames2.loadingDot, "data-delay": "2" })
24661
+ ] }) }) }),
24662
+ /* @__PURE__ */ jsx5("div", { ref: messagesEndRef })
24663
+ ]
24664
+ }
24665
+ ),
24644
24666
  /* @__PURE__ */ jsx5("div", { className: classNames2.inputContainer, children: /* @__PURE__ */ jsxs5("form", { onSubmit: handleSubmit, className: classNames2.inputForm, children: [
24645
24667
  /* @__PURE__ */ jsx5(
24646
24668
  "textarea",
@@ -24841,11 +24863,13 @@ var createChatWidget = (target, props) => {
24841
24863
  var createWidget = ({
24842
24864
  widgetType,
24843
24865
  schema,
24866
+ enrich,
24844
24867
  render
24845
24868
  }) => ({
24846
24869
  widgetType,
24847
24870
  schema,
24848
- render: (widget) => render(widget)
24871
+ enrich,
24872
+ render: (props) => render(props)
24849
24873
  });
24850
24874
 
24851
24875
  // lib/widgets/default-widgets.tsx
@@ -24858,7 +24882,7 @@ var defaultChatWidgets = [
24858
24882
  profileUri: zod_default.string().optional(),
24859
24883
  details: zod_default.record(zod_default.any()).optional()
24860
24884
  }).describe("showing a person card with name, photo and additional details"),
24861
- render: (widget) => /* @__PURE__ */ jsx8(
24885
+ render: ({ payload }) => /* @__PURE__ */ jsx8(
24862
24886
  "div",
24863
24887
  {
24864
24888
  style: {
@@ -24872,11 +24896,11 @@ var defaultChatWidgets = [
24872
24896
  boxShadow: "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)"
24873
24897
  },
24874
24898
  children: /* @__PURE__ */ jsxs7("div", { style: { display: "flex", alignItems: "flex-start", gap: "0.75rem" }, children: [
24875
- widget.profileUri && /* @__PURE__ */ jsx8(
24899
+ payload.profileUri && /* @__PURE__ */ jsx8(
24876
24900
  "img",
24877
24901
  {
24878
- src: widget.profileUri,
24879
- alt: widget.name,
24902
+ src: payload.profileUri,
24903
+ alt: payload.name,
24880
24904
  style: {
24881
24905
  width: "3rem",
24882
24906
  height: "3rem",
@@ -24888,8 +24912,8 @@ var defaultChatWidgets = [
24888
24912
  }
24889
24913
  ),
24890
24914
  /* @__PURE__ */ jsxs7("div", { style: { flex: 1, minWidth: 0 }, children: [
24891
- /* @__PURE__ */ jsx8("div", { style: { fontWeight: 600, fontSize: "1rem", color: "#111827", marginBottom: "0.25rem" }, children: widget.name }),
24892
- widget.details ? /* @__PURE__ */ jsx8("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: Object.entries(widget.details).map(([key, value]) => /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: "0.5rem", fontSize: "0.875rem" }, children: [
24915
+ /* @__PURE__ */ jsx8("div", { style: { fontWeight: 600, fontSize: "1rem", color: "#111827", marginBottom: "0.25rem" }, children: payload.name }),
24916
+ payload.details ? /* @__PURE__ */ jsx8("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: Object.entries(payload.details).map(([key, value]) => /* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: "0.5rem", fontSize: "0.875rem" }, children: [
24893
24917
  /* @__PURE__ */ jsxs7("span", { style: { color: "#6b7280", fontWeight: 500, minWidth: "fit-content" }, children: [
24894
24918
  key,
24895
24919
  ":"
@@ -24907,10 +24931,10 @@ var defaultChatWidgets = [
24907
24931
  uri: zod_default.string().url(),
24908
24932
  text: zod_default.string().optional()
24909
24933
  }).describe("rendering a clickable link"),
24910
- render: (widget) => /* @__PURE__ */ jsx8(
24934
+ render: ({ payload }) => /* @__PURE__ */ jsx8(
24911
24935
  "a",
24912
24936
  {
24913
- href: widget.uri,
24937
+ href: payload.uri,
24914
24938
  target: "_blank",
24915
24939
  rel: "noreferrer",
24916
24940
  style: {
@@ -24936,9 +24960,68 @@ var defaultChatWidgets = [
24936
24960
  e.currentTarget.style.borderColor = "#d1d5db";
24937
24961
  e.currentTarget.style.color = "#3b82f6";
24938
24962
  },
24939
- children: widget.text || widget.uri
24963
+ children: payload.text || payload.uri
24940
24964
  }
24941
24965
  )
24966
+ }),
24967
+ createWidget({
24968
+ widgetType: "vendor-cards",
24969
+ schema: zod_default.object({
24970
+ vendors: zod_default.array(zod_default.object({
24971
+ id: zod_default.string(),
24972
+ rank: zod_default.number().describe("ranking position of the vendor from 1 to N"),
24973
+ reason: zod_default.string().describe("reason for the vendor ranking")
24974
+ }))
24975
+ }).describe("displaying a list of vendor cards with rankings and reasons"),
24976
+ enrich: {
24977
+ toolId: "LEsUueB52QpakvN1n5HG",
24978
+ inputs: {
24979
+ ids: "${vendors|map('id')|join(',')}"
24980
+ }
24981
+ },
24982
+ render: ({ payload, enrichedResult }) => {
24983
+ const vendorData = enrichedResult?.data ?? {};
24984
+ return /* @__PURE__ */ jsx8("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: payload.vendors.map((v) => {
24985
+ const data = vendorData[v.id];
24986
+ return /* @__PURE__ */ jsxs7(
24987
+ "div",
24988
+ {
24989
+ style: {
24990
+ display: "flex",
24991
+ flexDirection: "column",
24992
+ gap: "0.5rem",
24993
+ padding: "1rem",
24994
+ background: "#ffffff",
24995
+ border: "1px solid #e5e7eb",
24996
+ borderRadius: "0.5rem",
24997
+ boxShadow: "0 1px 3px 0 rgba(0, 0, 0, 0.1)"
24998
+ },
24999
+ children: [
25000
+ /* @__PURE__ */ jsxs7("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
25001
+ /* @__PURE__ */ jsxs7("span", { style: { fontWeight: 600, fontSize: "1rem", color: "#111827" }, children: [
25002
+ "#",
25003
+ v.rank,
25004
+ " ",
25005
+ data?.name ?? `Vendor ${v.id}`
25006
+ ] }),
25007
+ data?.rating && /* @__PURE__ */ jsxs7("span", { style: { fontSize: "0.875rem", color: "#6b7280" }, children: [
25008
+ "\u2B50 ",
25009
+ data.rating.toFixed(1)
25010
+ ] })
25011
+ ] }),
25012
+ data?.description && /* @__PURE__ */ jsx8("div", { style: { fontSize: "0.875rem", color: "#6b7280" }, children: data.description }),
25013
+ /* @__PURE__ */ jsxs7("div", { style: { fontSize: "0.875rem", color: "#374151" }, children: [
25014
+ /* @__PURE__ */ jsx8("strong", { children: "Why:" }),
25015
+ " ",
25016
+ v.reason
25017
+ ] }),
25018
+ data?.address && /* @__PURE__ */ jsx8("div", { style: { fontSize: "0.75rem", color: "#9ca3af" }, children: data.address })
25019
+ ]
25020
+ },
25021
+ v.id
25022
+ );
25023
+ }) });
25024
+ }
24942
25025
  })
24943
25026
  ];
24944
25027
  export {