@databricks/appkit-ui 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CLAUDE.md +1 -0
  2. package/README.md +3 -20
  3. package/dist/cli/commands/setup.js +2 -2
  4. package/dist/cli/commands/setup.js.map +1 -1
  5. package/dist/js/config.d.ts +24 -0
  6. package/dist/js/config.d.ts.map +1 -0
  7. package/dist/js/config.js +49 -0
  8. package/dist/js/config.js.map +1 -0
  9. package/dist/js/index.d.ts +2 -1
  10. package/dist/js/index.js +2 -1
  11. package/dist/react/genie/genie-chat-message-list.d.ts.map +1 -1
  12. package/dist/react/genie/genie-chat-message-list.js +5 -4
  13. package/dist/react/genie/genie-chat-message-list.js.map +1 -1
  14. package/dist/react/genie/genie-chat-message.d.ts.map +1 -1
  15. package/dist/react/genie/genie-chat-message.js +7 -6
  16. package/dist/react/genie/genie-chat-message.js.map +1 -1
  17. package/dist/react/genie/genie-query-visualization.d.ts.map +1 -1
  18. package/dist/react/genie/genie-query-visualization.js +4 -3
  19. package/dist/react/genie/genie-query-visualization.js.map +1 -1
  20. package/dist/react/genie/index.d.ts +1 -1
  21. package/dist/react/genie/types.d.ts +2 -1
  22. package/dist/react/genie/types.d.ts.map +1 -1
  23. package/dist/react/genie/types.js +6 -0
  24. package/dist/react/genie/types.js.map +1 -0
  25. package/dist/react/genie/use-genie-chat.d.ts.map +1 -1
  26. package/dist/react/genie/use-genie-chat.js +60 -23
  27. package/dist/react/genie/use-genie-chat.js.map +1 -1
  28. package/dist/react/hooks/index.d.ts +2 -1
  29. package/dist/react/hooks/index.js +1 -0
  30. package/dist/react/hooks/use-plugin-config.d.ts +25 -0
  31. package/dist/react/hooks/use-plugin-config.d.ts.map +1 -0
  32. package/dist/react/hooks/use-plugin-config.js +32 -0
  33. package/dist/react/hooks/use-plugin-config.js.map +1 -0
  34. package/dist/react/index.d.ts +3 -2
  35. package/dist/react/index.js +2 -1
  36. package/dist/shared/src/index.d.ts +1 -1
  37. package/dist/shared/src/plugin.d.ts +12 -1
  38. package/dist/shared/src/plugin.d.ts.map +1 -0
  39. package/docs/api/appkit/Class.Plugin.md +75 -17
  40. package/docs/app-management.md +1 -1
  41. package/docs/architecture.md +1 -1
  42. package/docs/development/ai-assisted-development.md +2 -2
  43. package/docs/development/local-development.md +1 -1
  44. package/docs/development/remote-bridge.md +1 -1
  45. package/docs/development/templates.md +93 -0
  46. package/docs/development.md +1 -1
  47. package/docs/plugins/caching.md +3 -1
  48. package/docs/plugins/execution-context.md +1 -1
  49. package/docs/plugins/lakebase.md +1 -1
  50. package/docs.md +2 -2
  51. package/llms.txt +1 -0
  52. package/package.json +60 -58
  53. package/sbom.cdx.json +1 -0
@@ -1,5 +1,6 @@
1
1
  import { connectSSE } from "../../js/sse/connect-sse.js";
2
2
  import "../../js/index.js";
3
+ import { TERMINAL_STATUSES } from "./types.js";
3
4
  import { useCallback, useEffect, useRef, useState } from "react";
4
5
 
5
6
  //#region src/react/genie/use-genie-chat.ts
@@ -49,9 +50,22 @@ function makeAssistantItem(msg) {
49
50
  /**
50
51
  * The API bundles user question (content) and AI answer (attachments) in one message.
51
52
  * Split into separate user + assistant items for display.
53
+ *
54
+ * When a message is still in-progress (non-terminal status) and has no
55
+ * attachments yet, we emit an empty assistant placeholder so the UI can
56
+ * show a loading indicator and later poll for the completed response.
52
57
  */
53
58
  function messageResultToItems(msg) {
54
- if (!((msg.attachments?.length ?? 0) > 0)) return [makeUserItem(msg)];
59
+ const hasAttachments = (msg.attachments?.length ?? 0) > 0;
60
+ if (!hasAttachments && TERMINAL_STATUSES.has(msg.status)) return [makeUserItem(msg)];
61
+ if (!hasAttachments) return [makeUserItem(msg, "-user"), {
62
+ id: msg.messageId,
63
+ role: "assistant",
64
+ content: "",
65
+ status: msg.status,
66
+ attachments: [],
67
+ queryResults: /* @__PURE__ */ new Map()
68
+ }];
55
69
  return [makeUserItem(msg, "-user"), makeAssistantItem(msg)];
56
70
  }
57
71
  /**
@@ -118,12 +132,12 @@ function useGenieChat(options) {
118
132
  const conversationIdRef = useRef(null);
119
133
  const nextPageTokenRef = useRef(null);
120
134
  const isLoadingOlderRef = useRef(false);
135
+ const processStreamEventRef = useRef(() => {});
121
136
  useEffect(() => {
122
137
  conversationIdRef.current = conversationId;
123
138
  nextPageTokenRef.current = nextPageToken;
124
139
  }, [conversationId, nextPageToken]);
125
- /** Process SSE events during live message streaming (sendMessage). */
126
- const processStreamEvent = useCallback((event) => {
140
+ processStreamEventRef.current = useCallback((event) => {
127
141
  switch (event.type) {
128
142
  case "message_start":
129
143
  setConversationId(event.conversationId);
@@ -140,15 +154,13 @@ function useGenieChat(options) {
140
154
  });
141
155
  break;
142
156
  case "message_result": {
143
- const msg = event.message;
144
- if ((msg.attachments?.length ?? 0) > 0) {
145
- const item = makeAssistantItem(msg);
146
- setMessages((prev) => {
147
- const last = prev[prev.length - 1];
148
- if (last?.role === "assistant" && last.id === "") return [...prev.slice(0, -1), item];
149
- return [...prev, item];
150
- });
151
- }
157
+ const item = makeAssistantItem(event.message);
158
+ setMessages((prev) => {
159
+ const last = prev[prev.length - 1];
160
+ if (!last || last.role !== "assistant") return prev;
161
+ if (last.id === event.message.messageId || last.id === "") return [...prev.slice(0, -1), item];
162
+ return prev;
163
+ });
152
164
  break;
153
165
  }
154
166
  case "query_result":
@@ -213,7 +225,7 @@ function useGenieChat(options) {
213
225
  signal: abortController.signal,
214
226
  onMessage: async (message) => {
215
227
  try {
216
- processStreamEvent(JSON.parse(message.data));
228
+ processStreamEventRef.current(JSON.parse(message.data));
217
229
  } catch {}
218
230
  },
219
231
  onError: (err) => {
@@ -227,12 +239,12 @@ function useGenieChat(options) {
227
239
  }
228
240
  }).then(() => {
229
241
  if (!abortController.signal.aborted) setStatus((prev) => prev === "error" ? "error" : "idle");
242
+ }).catch(() => {
243
+ if (abortController.signal.aborted) return;
244
+ setError("Connection error. Please try again.");
245
+ setStatus("error");
230
246
  });
231
- }, [
232
- alias,
233
- basePath,
234
- processStreamEvent
235
- ]);
247
+ }, [alias, basePath]);
236
248
  /** Creates an AbortController, stores it in the given ref, and fetches a conversation page. */
237
249
  const fetchPage = useCallback((controllerRef, convId, options) => {
238
250
  controllerRef.current?.abort();
@@ -256,6 +268,30 @@ function useGenieChat(options) {
256
268
  abortController
257
269
  };
258
270
  }, [alias, basePath]);
271
+ const pollPendingMessage = useCallback((convId, messageId, parentAbortController) => {
272
+ setStatus("streaming");
273
+ const requestId = crypto.randomUUID();
274
+ connectSSE({
275
+ url: `${basePath}/${encodeURIComponent(alias)}/conversations/${encodeURIComponent(convId)}/messages/${encodeURIComponent(messageId)}?requestId=${encodeURIComponent(requestId)}`,
276
+ signal: parentAbortController.signal,
277
+ onMessage: async (message) => {
278
+ try {
279
+ processStreamEventRef.current(JSON.parse(message.data));
280
+ } catch {}
281
+ },
282
+ onError: (err) => {
283
+ if (parentAbortController.signal.aborted) return;
284
+ setError(err instanceof Error ? err.message : "Failed to poll pending message.");
285
+ setStatus("error");
286
+ }
287
+ }).then(() => {
288
+ if (!parentAbortController.signal.aborted) setStatus((prev) => prev === "error" ? "error" : "idle");
289
+ }).catch(() => {
290
+ if (parentAbortController.signal.aborted) return;
291
+ setError("Failed to poll pending message.");
292
+ setStatus("error");
293
+ });
294
+ }, [alias, basePath]);
259
295
  const loadHistory = useCallback((convId) => {
260
296
  paginationAbortRef.current?.abort();
261
297
  setStatus("loading-history");
@@ -264,12 +300,13 @@ function useGenieChat(options) {
264
300
  setConversationId(convId);
265
301
  const { promise, abortController } = fetchPage(abortControllerRef, convId, { errorMessage: "Failed to load conversation history." });
266
302
  promise.then((items) => {
267
- if (!abortController.signal.aborted) {
268
- setMessages(items);
269
- setStatus((prev) => prev === "error" ? "error" : "idle");
270
- }
303
+ if (abortController.signal.aborted) return;
304
+ setMessages(items);
305
+ const lastItem = items[items.length - 1];
306
+ if (lastItem?.role === "assistant" && !TERMINAL_STATUSES.has(lastItem.status)) pollPendingMessage(convId, lastItem.id, abortController);
307
+ else setStatus((prev) => prev === "error" ? "error" : "idle");
271
308
  });
272
- }, [fetchPage]);
309
+ }, [fetchPage, pollPendingMessage]);
273
310
  const fetchPreviousPage = useCallback(() => {
274
311
  if (!nextPageTokenRef.current || !conversationIdRef.current || isLoadingOlderRef.current) return;
275
312
  isLoadingOlderRef.current = true;
@@ -1 +1 @@
1
- {"version":3,"file":"use-genie-chat.js","names":[],"sources":["../../../src/react/genie/use-genie-chat.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { connectSSE } from \"@/js\";\nimport type {\n GenieChatStatus,\n GenieMessageItem,\n GenieMessageResponse,\n GenieStreamEvent,\n UseGenieChatOptions,\n UseGenieChatReturn,\n} from \"./types\";\n\nfunction getUrlParam(name: string): string | null {\n return new URLSearchParams(window.location.search).get(name);\n}\n\nfunction setUrlParam(name: string, value: string): void {\n const url = new URL(window.location.href);\n url.searchParams.set(name, value);\n window.history.replaceState({}, \"\", url.toString());\n}\n\nfunction removeUrlParam(name: string): void {\n const url = new URL(window.location.href);\n url.searchParams.delete(name);\n window.history.replaceState({}, \"\", url.toString());\n}\n\n/**\n * The Genie API puts the user's question in `message.content` and the\n * actual AI answer in text attachments. Extract the text attachment\n * content so we display the real answer, not the question echo.\n */\nfunction extractAssistantContent(msg: GenieMessageResponse): string {\n const textParts = (msg.attachments ?? [])\n .map((att) => att.text?.content)\n .filter(Boolean) as string[];\n return textParts.length > 0 ? textParts.join(\"\\n\\n\") : msg.content;\n}\n\nfunction makeUserItem(\n msg: GenieMessageResponse,\n idSuffix = \"\",\n): GenieMessageItem {\n return {\n id: `${msg.messageId}${idSuffix}`,\n role: \"user\",\n content: msg.content,\n status: msg.status,\n attachments: [],\n queryResults: new Map(),\n };\n}\n\nfunction makeAssistantItem(msg: GenieMessageResponse): GenieMessageItem {\n return {\n id: msg.messageId,\n role: \"assistant\",\n content: extractAssistantContent(msg),\n status: msg.status,\n attachments: msg.attachments ?? [],\n queryResults: new Map(),\n error: msg.error,\n };\n}\n\n/**\n * The API bundles user question (content) and AI answer (attachments) in one message.\n * Split into separate user + assistant items for display.\n */\nfunction messageResultToItems(msg: GenieMessageResponse): GenieMessageItem[] {\n const hasAttachments = (msg.attachments?.length ?? 0) > 0;\n if (!hasAttachments) return [makeUserItem(msg)];\n return [makeUserItem(msg, \"-user\"), makeAssistantItem(msg)];\n}\n\n/**\n * Streams a conversation page via SSE. Collects message items and query\n * results into a buffer and returns them when the stream completes.\n */\nfunction fetchConversationPage(\n basePath: string,\n alias: string,\n convId: string,\n options: {\n pageToken?: string;\n signal?: AbortSignal;\n onPaginationInfo?: (nextPageToken: string | null) => void;\n onError?: (error: string) => void;\n onConnectionError?: (err: unknown) => void;\n },\n): Promise<GenieMessageItem[]> {\n const params = new URLSearchParams({\n requestId: crypto.randomUUID(),\n });\n if (options.pageToken) {\n params.set(\"pageToken\", options.pageToken);\n }\n\n const items: GenieMessageItem[] = [];\n return connectSSE({\n url: `${basePath}/${encodeURIComponent(alias)}/conversations/${encodeURIComponent(convId)}?${params}`,\n signal: options.signal,\n onMessage: async (message) => {\n try {\n const event = JSON.parse(message.data) as GenieStreamEvent;\n switch (event.type) {\n case \"message_result\":\n items.push(...messageResultToItems(event.message));\n break;\n case \"query_result\":\n for (let i = items.length - 1; i >= 0; i--) {\n const item = items[i];\n if (\n item.attachments.some(\n (a) => a.attachmentId === event.attachmentId,\n )\n ) {\n item.queryResults.set(event.attachmentId, event.data);\n break;\n }\n }\n break;\n case \"history_info\":\n options.onPaginationInfo?.(event.nextPageToken);\n break;\n case \"error\":\n options.onError?.(event.error);\n break;\n }\n } catch {\n // Malformed SSE data\n }\n },\n onError: (err) => options.onConnectionError?.(err),\n }).then(() => items);\n}\n\n/** Minimum time (ms) to hold the loading-older state so scroll inertia settles before prepending messages. */\nconst MIN_PREVIOUS_PAGE_LOAD_MS = 800;\n\n/**\n * Manages the full Genie chat lifecycle:\n * SSE streaming, conversation persistence via URL, and history replay.\n *\n * @example\n * ```tsx\n * const { messages, status, sendMessage, reset } = useGenieChat({ alias: \"demo\" });\n * ```\n */\nexport function useGenieChat(options: UseGenieChatOptions): UseGenieChatReturn {\n const {\n alias,\n basePath = \"/api/genie\",\n persistInUrl = true,\n urlParamName = \"conversationId\",\n } = options;\n\n const [messages, setMessages] = useState<GenieMessageItem[]>([]);\n const [status, setStatus] = useState<GenieChatStatus>(\"idle\");\n const [conversationId, setConversationId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [nextPageToken, setNextPageToken] = useState<string | null>(null);\n\n const hasPreviousPage = nextPageToken !== null;\n const isFetchingPreviousPage = status === \"loading-older\";\n\n const abortControllerRef = useRef<AbortController | null>(null);\n const paginationAbortRef = useRef<AbortController | null>(null);\n const conversationIdRef = useRef<string | null>(null);\n const nextPageTokenRef = useRef<string | null>(null);\n const isLoadingOlderRef = useRef(false);\n\n useEffect(() => {\n conversationIdRef.current = conversationId;\n nextPageTokenRef.current = nextPageToken;\n }, [conversationId, nextPageToken]);\n\n /** Process SSE events during live message streaming (sendMessage). */\n const processStreamEvent = useCallback(\n (event: GenieStreamEvent) => {\n switch (event.type) {\n case \"message_start\": {\n setConversationId(event.conversationId);\n if (persistInUrl) {\n setUrlParam(urlParamName, event.conversationId);\n }\n break;\n }\n\n case \"status\": {\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n if (last?.role === \"assistant\") {\n return [...prev.slice(0, -1), { ...last, status: event.status }];\n }\n return prev;\n });\n break;\n }\n\n case \"message_result\": {\n const msg = event.message;\n const hasAttachments = (msg.attachments?.length ?? 0) > 0;\n\n if (hasAttachments) {\n // During streaming we already appended the user message locally,\n // so only handle assistant results. Messages without attachments\n // are the user-message echo from the API — skip those.\n const item = makeAssistantItem(msg);\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n if (last?.role === \"assistant\" && last.id === \"\") {\n return [...prev.slice(0, -1), item];\n }\n return [...prev, item];\n });\n }\n break;\n }\n\n case \"query_result\": {\n setMessages((prev) => {\n // Reverse scan — query results typically match recent messages\n for (let i = prev.length - 1; i >= 0; i--) {\n const msg = prev[i];\n if (\n msg.attachments.some(\n (a) => a.attachmentId === event.attachmentId,\n )\n ) {\n const updated = prev.slice();\n updated[i] = {\n ...msg,\n queryResults: new Map(msg.queryResults).set(\n event.attachmentId,\n event.data,\n ),\n };\n return updated;\n }\n }\n return prev;\n });\n break;\n }\n\n case \"error\": {\n setError(event.error);\n setStatus(\"error\");\n break;\n }\n }\n },\n [persistInUrl, urlParamName],\n );\n\n const sendMessage = useCallback(\n (content: string) => {\n const trimmed = content.trim();\n if (!trimmed) return;\n\n abortControllerRef.current?.abort();\n paginationAbortRef.current?.abort();\n setError(null);\n setStatus(\"streaming\");\n\n const userMessage: GenieMessageItem = {\n id: crypto.randomUUID(),\n role: \"user\",\n content: trimmed,\n status: \"COMPLETED\",\n attachments: [],\n queryResults: new Map(),\n };\n\n const assistantPlaceholder: GenieMessageItem = {\n id: \"\",\n role: \"assistant\",\n content: \"\",\n status: \"ASKING_AI\",\n attachments: [],\n queryResults: new Map(),\n };\n\n setMessages((prev) => [...prev, userMessage, assistantPlaceholder]);\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n const requestId = crypto.randomUUID();\n\n connectSSE({\n url: `${basePath}/${encodeURIComponent(alias)}/messages?requestId=${encodeURIComponent(requestId)}`,\n payload: {\n content: trimmed,\n conversationId: conversationIdRef.current ?? undefined,\n },\n signal: abortController.signal,\n onMessage: async (message) => {\n try {\n processStreamEvent(JSON.parse(message.data) as GenieStreamEvent);\n } catch {\n // Malformed SSE data\n }\n },\n onError: (err) => {\n if (abortController.signal.aborted) return;\n setError(\n err instanceof Error\n ? err.message\n : \"Connection error. Please try again.\",\n );\n setStatus(\"error\");\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n return last?.role === \"assistant\" && last.id === \"\"\n ? prev.slice(0, -1)\n : prev;\n });\n },\n }).then(() => {\n if (!abortController.signal.aborted) {\n setStatus((prev) => (prev === \"error\" ? \"error\" : \"idle\"));\n }\n });\n },\n [alias, basePath, processStreamEvent],\n );\n\n /** Creates an AbortController, stores it in the given ref, and fetches a conversation page. */\n const fetchPage = useCallback(\n (\n controllerRef: { current: AbortController | null },\n convId: string,\n options?: { pageToken?: string; errorMessage?: string },\n ) => {\n controllerRef.current?.abort();\n const abortController = new AbortController();\n controllerRef.current = abortController;\n\n const promise = fetchConversationPage(basePath, alias, convId, {\n pageToken: options?.pageToken,\n signal: abortController.signal,\n onPaginationInfo: setNextPageToken,\n onError: (msg) => {\n setError(msg);\n setStatus(\"error\");\n },\n onConnectionError: (err) => {\n if (abortController.signal.aborted) return;\n setError(\n err instanceof Error\n ? err.message\n : (options?.errorMessage ?? \"Failed to load messages.\"),\n );\n setStatus(\"error\");\n },\n });\n\n return { promise, abortController };\n },\n [alias, basePath],\n );\n\n const loadHistory = useCallback(\n (convId: string) => {\n paginationAbortRef.current?.abort();\n setStatus(\"loading-history\");\n setError(null);\n setMessages([]);\n setConversationId(convId);\n\n const { promise, abortController } = fetchPage(\n abortControllerRef,\n convId,\n { errorMessage: \"Failed to load conversation history.\" },\n );\n promise.then((items) => {\n if (!abortController.signal.aborted) {\n setMessages(items);\n setStatus((prev) => (prev === \"error\" ? \"error\" : \"idle\"));\n }\n });\n },\n [fetchPage],\n );\n\n const fetchPreviousPage = useCallback(() => {\n if (\n !nextPageTokenRef.current ||\n !conversationIdRef.current ||\n isLoadingOlderRef.current\n )\n return;\n\n isLoadingOlderRef.current = true;\n setStatus(\"loading-older\");\n setError(null);\n\n const startTime = Date.now();\n const { promise, abortController } = fetchPage(\n paginationAbortRef,\n conversationIdRef.current,\n {\n pageToken: nextPageTokenRef.current,\n errorMessage: \"Failed to load older messages.\",\n },\n );\n promise\n .then(async (items) => {\n if (abortController.signal.aborted) return;\n const elapsed = Date.now() - startTime;\n if (elapsed < MIN_PREVIOUS_PAGE_LOAD_MS) {\n await new Promise((r) =>\n setTimeout(r, MIN_PREVIOUS_PAGE_LOAD_MS - elapsed),\n );\n }\n if (abortController.signal.aborted) return;\n if (items.length > 0) {\n setMessages((prev) => [...items, ...prev]);\n }\n setStatus((current) =>\n current === \"loading-older\" ? \"idle\" : current,\n );\n })\n .finally(() => {\n isLoadingOlderRef.current = false;\n });\n }, [fetchPage]);\n\n const reset = useCallback(() => {\n abortControllerRef.current?.abort();\n paginationAbortRef.current?.abort();\n setMessages([]);\n setConversationId(null);\n setError(null);\n setStatus(\"idle\");\n setNextPageToken(null);\n if (persistInUrl) {\n removeUrlParam(urlParamName);\n }\n }, [persistInUrl, urlParamName]);\n\n useEffect(() => {\n if (!persistInUrl) return;\n const existingId = getUrlParam(urlParamName);\n if (existingId) {\n loadHistory(existingId);\n }\n return () => {\n abortControllerRef.current?.abort();\n paginationAbortRef.current?.abort();\n };\n }, [persistInUrl, urlParamName, loadHistory]);\n\n return {\n messages,\n status,\n conversationId,\n error,\n sendMessage,\n reset,\n hasPreviousPage,\n isFetchingPreviousPage,\n fetchPreviousPage,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-genie-chat.js","names":[],"sources":["../../../src/react/genie/use-genie-chat.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { connectSSE } from \"@/js\";\nimport {\n type GenieChatStatus,\n type GenieMessageItem,\n type GenieMessageResponse,\n type GenieStreamEvent,\n TERMINAL_STATUSES,\n type UseGenieChatOptions,\n type UseGenieChatReturn,\n} from \"./types\";\n\nfunction getUrlParam(name: string): string | null {\n return new URLSearchParams(window.location.search).get(name);\n}\n\nfunction setUrlParam(name: string, value: string): void {\n const url = new URL(window.location.href);\n url.searchParams.set(name, value);\n window.history.replaceState({}, \"\", url.toString());\n}\n\nfunction removeUrlParam(name: string): void {\n const url = new URL(window.location.href);\n url.searchParams.delete(name);\n window.history.replaceState({}, \"\", url.toString());\n}\n\n/**\n * The Genie API puts the user's question in `message.content` and the\n * actual AI answer in text attachments. Extract the text attachment\n * content so we display the real answer, not the question echo.\n */\nfunction extractAssistantContent(msg: GenieMessageResponse): string {\n const textParts = (msg.attachments ?? [])\n .map((att) => att.text?.content)\n .filter(Boolean) as string[];\n return textParts.length > 0 ? textParts.join(\"\\n\\n\") : msg.content;\n}\n\nfunction makeUserItem(\n msg: GenieMessageResponse,\n idSuffix = \"\",\n): GenieMessageItem {\n return {\n id: `${msg.messageId}${idSuffix}`,\n role: \"user\",\n content: msg.content,\n status: msg.status,\n attachments: [],\n queryResults: new Map(),\n };\n}\n\nfunction makeAssistantItem(msg: GenieMessageResponse): GenieMessageItem {\n return {\n id: msg.messageId,\n role: \"assistant\",\n content: extractAssistantContent(msg),\n status: msg.status,\n attachments: msg.attachments ?? [],\n queryResults: new Map(),\n error: msg.error,\n };\n}\n\n/**\n * The API bundles user question (content) and AI answer (attachments) in one message.\n * Split into separate user + assistant items for display.\n *\n * When a message is still in-progress (non-terminal status) and has no\n * attachments yet, we emit an empty assistant placeholder so the UI can\n * show a loading indicator and later poll for the completed response.\n */\nfunction messageResultToItems(msg: GenieMessageResponse): GenieMessageItem[] {\n const hasAttachments = (msg.attachments?.length ?? 0) > 0;\n\n if (!hasAttachments && TERMINAL_STATUSES.has(msg.status)) {\n return [makeUserItem(msg)];\n }\n if (!hasAttachments) {\n return [\n makeUserItem(msg, \"-user\"),\n {\n id: msg.messageId,\n role: \"assistant\",\n content: \"\",\n status: msg.status,\n attachments: [],\n queryResults: new Map(),\n },\n ];\n }\n return [makeUserItem(msg, \"-user\"), makeAssistantItem(msg)];\n}\n\n/**\n * Streams a conversation page via SSE. Collects message items and query\n * results into a buffer and returns them when the stream completes.\n */\nfunction fetchConversationPage(\n basePath: string,\n alias: string,\n convId: string,\n options: {\n pageToken?: string;\n signal?: AbortSignal;\n onPaginationInfo?: (nextPageToken: string | null) => void;\n onError?: (error: string) => void;\n onConnectionError?: (err: unknown) => void;\n },\n): Promise<GenieMessageItem[]> {\n const params = new URLSearchParams({\n requestId: crypto.randomUUID(),\n });\n if (options.pageToken) {\n params.set(\"pageToken\", options.pageToken);\n }\n\n const items: GenieMessageItem[] = [];\n return connectSSE({\n url: `${basePath}/${encodeURIComponent(alias)}/conversations/${encodeURIComponent(convId)}?${params}`,\n signal: options.signal,\n onMessage: async (message) => {\n try {\n const event = JSON.parse(message.data) as GenieStreamEvent;\n switch (event.type) {\n case \"message_result\":\n items.push(...messageResultToItems(event.message));\n break;\n case \"query_result\":\n for (let i = items.length - 1; i >= 0; i--) {\n const item = items[i];\n if (\n item.attachments.some(\n (a) => a.attachmentId === event.attachmentId,\n )\n ) {\n item.queryResults.set(event.attachmentId, event.data);\n break;\n }\n }\n break;\n case \"history_info\":\n options.onPaginationInfo?.(event.nextPageToken);\n break;\n case \"error\":\n options.onError?.(event.error);\n break;\n }\n } catch {\n // Malformed SSE data\n }\n },\n onError: (err) => options.onConnectionError?.(err),\n }).then(() => items);\n}\n\n/** Minimum time (ms) to hold the loading-older state so scroll inertia settles before prepending messages. */\nconst MIN_PREVIOUS_PAGE_LOAD_MS = 800;\n\n/**\n * Manages the full Genie chat lifecycle:\n * SSE streaming, conversation persistence via URL, and history replay.\n *\n * @example\n * ```tsx\n * const { messages, status, sendMessage, reset } = useGenieChat({ alias: \"demo\" });\n * ```\n */\nexport function useGenieChat(options: UseGenieChatOptions): UseGenieChatReturn {\n const {\n alias,\n basePath = \"/api/genie\",\n persistInUrl = true,\n urlParamName = \"conversationId\",\n } = options;\n\n const [messages, setMessages] = useState<GenieMessageItem[]>([]);\n const [status, setStatus] = useState<GenieChatStatus>(\"idle\");\n const [conversationId, setConversationId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [nextPageToken, setNextPageToken] = useState<string | null>(null);\n\n const hasPreviousPage = nextPageToken !== null;\n const isFetchingPreviousPage = status === \"loading-older\";\n\n const abortControllerRef = useRef<AbortController | null>(null);\n const paginationAbortRef = useRef<AbortController | null>(null);\n const conversationIdRef = useRef<string | null>(null);\n const nextPageTokenRef = useRef<string | null>(null);\n const isLoadingOlderRef = useRef(false);\n const processStreamEventRef = useRef<(event: GenieStreamEvent) => void>(\n () => {},\n );\n\n useEffect(() => {\n conversationIdRef.current = conversationId;\n nextPageTokenRef.current = nextPageToken;\n }, [conversationId, nextPageToken]);\n\n /** Process SSE events during live message streaming (sendMessage). */\n const processStreamEvent = useCallback(\n (event: GenieStreamEvent) => {\n switch (event.type) {\n case \"message_start\": {\n setConversationId(event.conversationId);\n if (persistInUrl) {\n setUrlParam(urlParamName, event.conversationId);\n }\n break;\n }\n\n case \"status\": {\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n if (last?.role === \"assistant\") {\n return [...prev.slice(0, -1), { ...last, status: event.status }];\n }\n return prev;\n });\n break;\n }\n\n case \"message_result\": {\n const item = makeAssistantItem(event.message);\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n if (!last || last.role !== \"assistant\") return prev;\n\n if (last.id === event.message.messageId || last.id === \"\") {\n return [...prev.slice(0, -1), item];\n }\n\n return prev;\n });\n break;\n }\n\n case \"query_result\": {\n setMessages((prev) => {\n // Reverse scan — query results typically match recent messages\n for (let i = prev.length - 1; i >= 0; i--) {\n const msg = prev[i];\n if (\n msg.attachments.some(\n (a) => a.attachmentId === event.attachmentId,\n )\n ) {\n const updated = prev.slice();\n updated[i] = {\n ...msg,\n queryResults: new Map(msg.queryResults).set(\n event.attachmentId,\n event.data,\n ),\n };\n return updated;\n }\n }\n return prev;\n });\n break;\n }\n\n case \"error\": {\n setError(event.error);\n setStatus(\"error\");\n break;\n }\n }\n },\n [persistInUrl, urlParamName],\n );\n\n processStreamEventRef.current = processStreamEvent;\n\n const sendMessage = useCallback(\n (content: string) => {\n const trimmed = content.trim();\n if (!trimmed) return;\n\n abortControllerRef.current?.abort();\n paginationAbortRef.current?.abort();\n setError(null);\n setStatus(\"streaming\");\n\n const userMessage: GenieMessageItem = {\n id: crypto.randomUUID(),\n role: \"user\",\n content: trimmed,\n status: \"COMPLETED\",\n attachments: [],\n queryResults: new Map(),\n };\n\n const assistantPlaceholder: GenieMessageItem = {\n id: \"\",\n role: \"assistant\",\n content: \"\",\n status: \"ASKING_AI\",\n attachments: [],\n queryResults: new Map(),\n };\n\n setMessages((prev) => [...prev, userMessage, assistantPlaceholder]);\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n const requestId = crypto.randomUUID();\n\n connectSSE({\n url: `${basePath}/${encodeURIComponent(alias)}/messages?requestId=${encodeURIComponent(requestId)}`,\n payload: {\n content: trimmed,\n conversationId: conversationIdRef.current ?? undefined,\n },\n signal: abortController.signal,\n onMessage: async (message) => {\n try {\n processStreamEventRef.current(\n JSON.parse(message.data) as GenieStreamEvent,\n );\n } catch {\n // Malformed SSE data\n }\n },\n onError: (err) => {\n if (abortController.signal.aborted) return;\n setError(\n err instanceof Error\n ? err.message\n : \"Connection error. Please try again.\",\n );\n setStatus(\"error\");\n setMessages((prev) => {\n const last = prev[prev.length - 1];\n return last?.role === \"assistant\" && last.id === \"\"\n ? prev.slice(0, -1)\n : prev;\n });\n },\n })\n .then(() => {\n if (!abortController.signal.aborted) {\n setStatus((prev) => (prev === \"error\" ? \"error\" : \"idle\"));\n }\n })\n .catch(() => {\n if (abortController.signal.aborted) return;\n setError(\"Connection error. Please try again.\");\n setStatus(\"error\");\n });\n },\n [alias, basePath],\n );\n\n /** Creates an AbortController, stores it in the given ref, and fetches a conversation page. */\n const fetchPage = useCallback(\n (\n controllerRef: { current: AbortController | null },\n convId: string,\n options?: { pageToken?: string; errorMessage?: string },\n ) => {\n controllerRef.current?.abort();\n const abortController = new AbortController();\n controllerRef.current = abortController;\n\n const promise = fetchConversationPage(basePath, alias, convId, {\n pageToken: options?.pageToken,\n signal: abortController.signal,\n onPaginationInfo: setNextPageToken,\n onError: (msg) => {\n setError(msg);\n setStatus(\"error\");\n },\n onConnectionError: (err) => {\n if (abortController.signal.aborted) return;\n setError(\n err instanceof Error\n ? err.message\n : (options?.errorMessage ?? \"Failed to load messages.\"),\n );\n setStatus(\"error\");\n },\n });\n\n return { promise, abortController };\n },\n [alias, basePath],\n );\n\n const pollPendingMessage = useCallback(\n (\n convId: string,\n messageId: string,\n parentAbortController: AbortController,\n ) => {\n setStatus(\"streaming\");\n\n const requestId = crypto.randomUUID();\n const url =\n `${basePath}/${encodeURIComponent(alias)}/conversations/${encodeURIComponent(convId)}` +\n `/messages/${encodeURIComponent(messageId)}?requestId=${encodeURIComponent(requestId)}`;\n\n connectSSE({\n url,\n signal: parentAbortController.signal,\n onMessage: async (message) => {\n try {\n processStreamEventRef.current(\n JSON.parse(message.data) as GenieStreamEvent,\n );\n } catch {\n // Malformed SSE data\n }\n },\n onError: (err) => {\n if (parentAbortController.signal.aborted) return;\n setError(\n err instanceof Error\n ? err.message\n : \"Failed to poll pending message.\",\n );\n setStatus(\"error\");\n },\n })\n .then(() => {\n if (!parentAbortController.signal.aborted) {\n setStatus((prev) => (prev === \"error\" ? \"error\" : \"idle\"));\n }\n })\n .catch(() => {\n if (parentAbortController.signal.aborted) return;\n setError(\"Failed to poll pending message.\");\n setStatus(\"error\");\n });\n },\n [alias, basePath],\n );\n\n const loadHistory = useCallback(\n (convId: string) => {\n paginationAbortRef.current?.abort();\n setStatus(\"loading-history\");\n setError(null);\n setMessages([]);\n setConversationId(convId);\n\n const { promise, abortController } = fetchPage(\n abortControllerRef,\n convId,\n { errorMessage: \"Failed to load conversation history.\" },\n );\n promise.then((items) => {\n if (abortController.signal.aborted) return;\n setMessages(items);\n\n const lastItem = items[items.length - 1];\n if (\n lastItem?.role === \"assistant\" &&\n !TERMINAL_STATUSES.has(lastItem.status)\n ) {\n pollPendingMessage(convId, lastItem.id, abortController);\n } else {\n setStatus((prev) => (prev === \"error\" ? \"error\" : \"idle\"));\n }\n });\n },\n [fetchPage, pollPendingMessage],\n );\n\n const fetchPreviousPage = useCallback(() => {\n if (\n !nextPageTokenRef.current ||\n !conversationIdRef.current ||\n isLoadingOlderRef.current\n )\n return;\n\n isLoadingOlderRef.current = true;\n setStatus(\"loading-older\");\n setError(null);\n\n const startTime = Date.now();\n const { promise, abortController } = fetchPage(\n paginationAbortRef,\n conversationIdRef.current,\n {\n pageToken: nextPageTokenRef.current,\n errorMessage: \"Failed to load older messages.\",\n },\n );\n promise\n .then(async (items) => {\n if (abortController.signal.aborted) return;\n const elapsed = Date.now() - startTime;\n if (elapsed < MIN_PREVIOUS_PAGE_LOAD_MS) {\n await new Promise((r) =>\n setTimeout(r, MIN_PREVIOUS_PAGE_LOAD_MS - elapsed),\n );\n }\n if (abortController.signal.aborted) return;\n if (items.length > 0) {\n setMessages((prev) => [...items, ...prev]);\n }\n setStatus((current) =>\n current === \"loading-older\" ? \"idle\" : current,\n );\n })\n .finally(() => {\n isLoadingOlderRef.current = false;\n });\n }, [fetchPage]);\n\n const reset = useCallback(() => {\n abortControllerRef.current?.abort();\n paginationAbortRef.current?.abort();\n setMessages([]);\n setConversationId(null);\n setError(null);\n setStatus(\"idle\");\n setNextPageToken(null);\n if (persistInUrl) {\n removeUrlParam(urlParamName);\n }\n }, [persistInUrl, urlParamName]);\n\n useEffect(() => {\n if (!persistInUrl) return;\n const existingId = getUrlParam(urlParamName);\n if (existingId) {\n loadHistory(existingId);\n }\n return () => {\n abortControllerRef.current?.abort();\n paginationAbortRef.current?.abort();\n };\n }, [persistInUrl, urlParamName, loadHistory]);\n\n return {\n messages,\n status,\n conversationId,\n error,\n sendMessage,\n reset,\n hasPreviousPage,\n isFetchingPreviousPage,\n fetchPreviousPage,\n };\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
1
  import { UseChartDataOptions, UseChartDataResult, useChartData } from "./use-chart-data.js";
2
2
  import { AnalyticsFormat, InferResultByFormat, InferRowType, PluginRegistry, QueryRegistry, TypedArrowTable, UseAnalyticsQueryOptions, UseAnalyticsQueryResult } from "./types.js";
3
- import { useAnalyticsQuery } from "./use-analytics-query.js";
3
+ import { useAnalyticsQuery } from "./use-analytics-query.js";
4
+ import { usePluginClientConfig } from "./use-plugin-config.js";
@@ -1,2 +1,3 @@
1
1
  import { useAnalyticsQuery } from "./use-analytics-query.js";
2
2
  import { useChartData } from "./use-chart-data.js";
3
+ import { usePluginClientConfig } from "./use-plugin-config.js";
@@ -0,0 +1,25 @@
1
+ //#region src/react/hooks/use-plugin-config.d.ts
2
+ /**
3
+ * Returns the client-side config exposed by a plugin's `clientConfig()` method.
4
+ *
5
+ * The value is read once from the boot-time `<script id="__appkit__">` payload
6
+ * and cached for the lifetime of the component. Because `clientConfig()` runs
7
+ * at server startup, the data is static and never changes during a session.
8
+ *
9
+ * @param pluginName - The plugin name as registered in AppKit (e.g. `"files"`)
10
+ * @returns The plugin's client config object, typed via the generic parameter
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * interface FilesConfig { volumes: string[] }
15
+ *
16
+ * function MyComponent() {
17
+ * const { volumes } = usePluginClientConfig<FilesConfig>("files");
18
+ * return <ul>{volumes.map(v => <li key={v}>{v}</li>)}</ul>;
19
+ * }
20
+ * ```
21
+ */
22
+ declare function usePluginClientConfig<T = Record<string, unknown>>(pluginName: string): T;
23
+ //#endregion
24
+ export { usePluginClientConfig };
25
+ //# sourceMappingURL=use-plugin-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-plugin-config.d.ts","names":[],"sources":["../../../src/react/hooks/use-plugin-config.ts"],"mappings":";;AAuBA;;;;;;;;;;;;;;;;;;;iBAAgB,qBAAA,KAA0B,MAAA,kBAAA,CACxC,UAAA,WACC,CAAA"}
@@ -0,0 +1,32 @@
1
+ import { getPluginClientConfig } from "../../js/config.js";
2
+ import "../../js/index.js";
3
+ import { useMemo } from "react";
4
+
5
+ //#region src/react/hooks/use-plugin-config.ts
6
+ /**
7
+ * Returns the client-side config exposed by a plugin's `clientConfig()` method.
8
+ *
9
+ * The value is read once from the boot-time `<script id="__appkit__">` payload
10
+ * and cached for the lifetime of the component. Because `clientConfig()` runs
11
+ * at server startup, the data is static and never changes during a session.
12
+ *
13
+ * @param pluginName - The plugin name as registered in AppKit (e.g. `"files"`)
14
+ * @returns The plugin's client config object, typed via the generic parameter
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * interface FilesConfig { volumes: string[] }
19
+ *
20
+ * function MyComponent() {
21
+ * const { volumes } = usePluginClientConfig<FilesConfig>("files");
22
+ * return <ul>{volumes.map(v => <li key={v}>{v}</li>)}</ul>;
23
+ * }
24
+ * ```
25
+ */
26
+ function usePluginClientConfig(pluginName) {
27
+ return useMemo(() => getPluginClientConfig(pluginName), [pluginName]);
28
+ }
29
+
30
+ //#endregion
31
+ export { usePluginClientConfig };
32
+ //# sourceMappingURL=use-plugin-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-plugin-config.js","names":[],"sources":["../../../src/react/hooks/use-plugin-config.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { getPluginClientConfig } from \"@/js\";\n\n/**\n * Returns the client-side config exposed by a plugin's `clientConfig()` method.\n *\n * The value is read once from the boot-time `<script id=\"__appkit__\">` payload\n * and cached for the lifetime of the component. Because `clientConfig()` runs\n * at server startup, the data is static and never changes during a session.\n *\n * @param pluginName - The plugin name as registered in AppKit (e.g. `\"files\"`)\n * @returns The plugin's client config object, typed via the generic parameter\n *\n * @example\n * ```tsx\n * interface FilesConfig { volumes: string[] }\n *\n * function MyComponent() {\n * const { volumes } = usePluginClientConfig<FilesConfig>(\"files\");\n * return <ul>{volumes.map(v => <li key={v}>{v}</li>)}</ul>;\n * }\n * ```\n */\nexport function usePluginClientConfig<T = Record<string, unknown>>(\n pluginName: string,\n): T {\n return useMemo(() => getPluginClientConfig<T>(pluginName), [pluginName]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,sBACd,YACG;AACH,QAAO,cAAc,sBAAyB,WAAW,EAAE,CAAC,WAAW,CAAC"}
@@ -28,7 +28,7 @@ import { NewFolderInput, NewFolderInputProps } from "./file-browser/new-folder-i
28
28
  import "./file-browser/index.js";
29
29
  import { ColumnCategory, GenieColumnMeta, TransformedGenieData, transformGenieData } from "./genie/genie-query-transform.js";
30
30
  import { ChartInference, getCompatibleChartTypes, inferChartType } from "./genie/genie-chart-inference.js";
31
- import { GenieChatProps, GenieChatStatus, GenieMessageItem, UseGenieChatOptions, UseGenieChatReturn } from "./genie/types.js";
31
+ import { GenieChatProps, GenieChatStatus, GenieMessageItem, TERMINAL_STATUSES, UseGenieChatOptions, UseGenieChatReturn } from "./genie/types.js";
32
32
  import { GenieChat } from "./genie/genie-chat.js";
33
33
  import { GenieChatInput } from "./genie/genie-chat-input.js";
34
34
  import { GenieChatMessage } from "./genie/genie-chat-message.js";
@@ -38,6 +38,7 @@ import { useGenieChat } from "./genie/use-genie-chat.js";
38
38
  import "./genie/index.js";
39
39
  import { AnalyticsFormat, InferResultByFormat, InferRowType, PluginRegistry, QueryRegistry, TypedArrowTable, UseAnalyticsQueryOptions, UseAnalyticsQueryResult } from "./hooks/types.js";
40
40
  import { useAnalyticsQuery } from "./hooks/use-analytics-query.js";
41
+ import { usePluginClientConfig } from "./hooks/use-plugin-config.js";
41
42
  import "./hooks/index.js";
42
43
  import { cn } from "./lib/utils.js";
43
44
  import { PortalContainerContext, PortalContainerProvider, usePortalContainer, useResolvedPortalContainer } from "./portal-container-context.js";
@@ -97,4 +98,4 @@ import { Textarea } from "./ui/textarea.js";
97
98
  import { Toggle, toggleVariants } from "./ui/toggle.js";
98
99
  import { ToggleGroup, ToggleGroupItem } from "./ui/toggle-group.js";
99
100
  import "./ui/index.js";
100
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AnalyticsFormat, AreaChart, AreaChartProps, AreaChartSpecificProps, AspectRatio, Avatar, AvatarFallback, AvatarImage, Badge, BarChart, BarChartProps, BarChartSpecificProps, BaseChart, BaseChartProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, CHART_COLOR_VARS, CHART_COLOR_VARS_CATEGORICAL, CHART_COLOR_VARS_DIVERGING, CHART_COLOR_VARS_SEQUENTIAL, Calendar, CalendarDayButton, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, CarouselApi, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, CartesianContext, ChartBaseProps, ChartColorPalette, ChartConfig, ChartContainer, ChartData, ChartInference, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, ChartType, ChartWrapper, ChartWrapperProps, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, ColumnCategory, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DATE_FIELD_PATTERNS, DataFormat, DataProps, DataTable, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DirectoryEntry, DirectoryList, DirectoryListProps, DonutChart, DonutChartProps, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, FALLBACK_COLORS_CATEGORICAL, FALLBACK_COLORS_DIVERGING, FALLBACK_COLORS_SEQUENTIAL, Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle, FileBreadcrumb, FileBreadcrumbProps, FileBrowserLabels, FileEntry, FileEntryProps, FilePreview, FilePreviewPanel, FilePreviewPanelProps, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, GenieAttachmentResponse, GenieChat, GenieChatInput, GenieChatMessage, GenieChatMessageList, GenieChatProps, GenieChatStatus, GenieColumnMeta, GenieMessageItem, GenieMessageResponse, GenieQueryVisualization, GenieStatementResponse, GenieStreamEvent, HeatmapChart, HeatmapChartProps, HeatmapChartSpecificProps, HeatmapContext, HoverCard, HoverCardContent, HoverCardTrigger, InferResultByFormat, InferRowType, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle, Kbd, KbdGroup, Label, LineChart, LineChartProps, LineChartSpecificProps, METADATA_DATE_PATTERNS, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, NAME_FIELD_PATTERNS, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, NewFolderInput, NewFolderInputProps, NormalizedChartData, NormalizedChartDataBase, NormalizedHeatmapData, OptionBuilderContext, Orientation, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PieChart, PieChartProps, PieChartSpecificProps, PluginRegistry, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, PortalContainerContext, PortalContainerProvider, Progress, QueryProps, QueryRegistry, RadarChart, RadarChartProps, RadarChartSpecificProps, RadioGroup, RadioGroupItem, ResizableHandle, ResizablePanel, ResizablePanelGroup, ScatterChart, ScatterChartProps, ScatterChartSpecificProps, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, Slider, Spinner, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, TransformedGenieData, TypedArrowTable, UnifiedChartProps, UseAnalyticsQueryOptions, UseAnalyticsQueryResult, UseChartDataOptions, UseChartDataResult, UseGenieChatOptions, UseGenieChatReturn, badgeVariants, buildCartesianOption, buildHeatmapOption, buildHorizontalBarOption, buildPieOption, buildRadarOption, buttonGroupVariants, buttonVariants, cn, createChart, createTimeSeriesData, formatFileSize, formatLabel, getCompatibleChartTypes, inferChartType, isArrowTable, isDataProps, isQueryProps, navigationMenuTriggerStyle, normalizeChartData, normalizeHeatmapData, sortTimeSeriesAscending, toChartArray, toChartValue, toggleVariants, transformGenieData, truncateLabel, useAllThemeColors, useAnalyticsQuery, useChartData, useFormField, useGenieChat, usePortalContainer, useResolvedPortalContainer, useSidebar, useThemeColors };
101
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AnalyticsFormat, AreaChart, AreaChartProps, AreaChartSpecificProps, AspectRatio, Avatar, AvatarFallback, AvatarImage, Badge, BarChart, BarChartProps, BarChartSpecificProps, BaseChart, BaseChartProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, CHART_COLOR_VARS, CHART_COLOR_VARS_CATEGORICAL, CHART_COLOR_VARS_DIVERGING, CHART_COLOR_VARS_SEQUENTIAL, Calendar, CalendarDayButton, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, CarouselApi, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, CartesianContext, ChartBaseProps, ChartColorPalette, ChartConfig, ChartContainer, ChartData, ChartInference, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, ChartType, ChartWrapper, ChartWrapperProps, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, ColumnCategory, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DATE_FIELD_PATTERNS, DataFormat, DataProps, DataTable, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DirectoryEntry, DirectoryList, DirectoryListProps, DonutChart, DonutChartProps, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, FALLBACK_COLORS_CATEGORICAL, FALLBACK_COLORS_DIVERGING, FALLBACK_COLORS_SEQUENTIAL, Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle, FileBreadcrumb, FileBreadcrumbProps, FileBrowserLabels, FileEntry, FileEntryProps, FilePreview, FilePreviewPanel, FilePreviewPanelProps, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, GenieAttachmentResponse, GenieChat, GenieChatInput, GenieChatMessage, GenieChatMessageList, GenieChatProps, GenieChatStatus, GenieColumnMeta, GenieMessageItem, GenieMessageResponse, GenieQueryVisualization, GenieStatementResponse, GenieStreamEvent, HeatmapChart, HeatmapChartProps, HeatmapChartSpecificProps, HeatmapContext, HoverCard, HoverCardContent, HoverCardTrigger, InferResultByFormat, InferRowType, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle, Kbd, KbdGroup, Label, LineChart, LineChartProps, LineChartSpecificProps, METADATA_DATE_PATTERNS, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, NAME_FIELD_PATTERNS, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, NewFolderInput, NewFolderInputProps, NormalizedChartData, NormalizedChartDataBase, NormalizedHeatmapData, OptionBuilderContext, Orientation, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PieChart, PieChartProps, PieChartSpecificProps, PluginRegistry, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, PortalContainerContext, PortalContainerProvider, Progress, QueryProps, QueryRegistry, RadarChart, RadarChartProps, RadarChartSpecificProps, RadioGroup, RadioGroupItem, ResizableHandle, ResizablePanel, ResizablePanelGroup, ScatterChart, ScatterChartProps, ScatterChartSpecificProps, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, Slider, Spinner, Switch, TERMINAL_STATUSES, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, TransformedGenieData, TypedArrowTable, UnifiedChartProps, UseAnalyticsQueryOptions, UseAnalyticsQueryResult, UseChartDataOptions, UseChartDataResult, UseGenieChatOptions, UseGenieChatReturn, badgeVariants, buildCartesianOption, buildHeatmapOption, buildHorizontalBarOption, buildPieOption, buildRadarOption, buttonGroupVariants, buttonVariants, cn, createChart, createTimeSeriesData, formatFileSize, formatLabel, getCompatibleChartTypes, inferChartType, isArrowTable, isDataProps, isQueryProps, navigationMenuTriggerStyle, normalizeChartData, normalizeHeatmapData, sortTimeSeriesAscending, toChartArray, toChartValue, toggleVariants, transformGenieData, truncateLabel, useAllThemeColors, useAnalyticsQuery, useChartData, useFormField, useGenieChat, usePluginClientConfig, usePortalContainer, useResolvedPortalContainer, useSidebar, useThemeColors };
@@ -46,6 +46,7 @@ import { GenieChatMessageList } from "./genie/genie-chat-message-list.js";
46
46
  import { useGenieChat } from "./genie/use-genie-chat.js";
47
47
  import { GenieChat } from "./genie/genie-chat.js";
48
48
  import "./genie/index.js";
49
+ import { usePluginClientConfig } from "./hooks/use-plugin-config.js";
49
50
  import "./hooks/index.js";
50
51
  import { Input } from "./ui/input.js";
51
52
  import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue } from "./ui/select.js";
@@ -94,4 +95,4 @@ import { Toggle, toggleVariants } from "./ui/toggle.js";
94
95
  import { ToggleGroup, ToggleGroupItem } from "./ui/toggle-group.js";
95
96
  import "./ui/index.js";
96
97
 
97
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AreaChart, AspectRatio, Avatar, AvatarFallback, AvatarImage, Badge, BarChart, BaseChart, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, CHART_COLOR_VARS, CHART_COLOR_VARS_CATEGORICAL, CHART_COLOR_VARS_DIVERGING, CHART_COLOR_VARS_SEQUENTIAL, Calendar, CalendarDayButton, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, ChartWrapper, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DATE_FIELD_PATTERNS, DataTable, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DirectoryList, DonutChart, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, FALLBACK_COLORS_CATEGORICAL, FALLBACK_COLORS_DIVERGING, FALLBACK_COLORS_SEQUENTIAL, Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle, FileBreadcrumb, FileEntry, FilePreviewPanel, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, GenieChat, GenieChatInput, GenieChatMessage, GenieChatMessageList, GenieQueryVisualization, HeatmapChart, HoverCard, HoverCardContent, HoverCardTrigger, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle, Kbd, KbdGroup, Label, LineChart, METADATA_DATE_PATTERNS, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, NAME_FIELD_PATTERNS, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, NewFolderInput, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PieChart, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, PortalContainerContext, PortalContainerProvider, Progress, RadarChart, RadioGroup, RadioGroupItem, ResizableHandle, ResizablePanel, ResizablePanelGroup, ScatterChart, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, Slider, Spinner, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buildCartesianOption, buildHeatmapOption, buildHorizontalBarOption, buildPieOption, buildRadarOption, buttonGroupVariants, buttonVariants, cn, createChart, createTimeSeriesData, formatFileSize, formatLabel, getCompatibleChartTypes, inferChartType, isArrowTable, isDataProps, isQueryProps, navigationMenuTriggerStyle, normalizeChartData, normalizeHeatmapData, sortTimeSeriesAscending, toChartArray, toChartValue, toggleVariants, transformGenieData, truncateLabel, useAllThemeColors, useAnalyticsQuery, useChartData, useFormField, useGenieChat, usePortalContainer, useResolvedPortalContainer, useSidebar, useThemeColors };
98
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AreaChart, AspectRatio, Avatar, AvatarFallback, AvatarImage, Badge, BarChart, BaseChart, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, CHART_COLOR_VARS, CHART_COLOR_VARS_CATEGORICAL, CHART_COLOR_VARS_DIVERGING, CHART_COLOR_VARS_SEQUENTIAL, Calendar, CalendarDayButton, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, ChartWrapper, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DATE_FIELD_PATTERNS, DataTable, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DirectoryList, DonutChart, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, FALLBACK_COLORS_CATEGORICAL, FALLBACK_COLORS_DIVERGING, FALLBACK_COLORS_SEQUENTIAL, Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle, FileBreadcrumb, FileEntry, FilePreviewPanel, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, GenieChat, GenieChatInput, GenieChatMessage, GenieChatMessageList, GenieQueryVisualization, HeatmapChart, HoverCard, HoverCardContent, HoverCardTrigger, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle, Kbd, KbdGroup, Label, LineChart, METADATA_DATE_PATTERNS, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, NAME_FIELD_PATTERNS, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, NewFolderInput, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PieChart, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, PortalContainerContext, PortalContainerProvider, Progress, RadarChart, RadioGroup, RadioGroupItem, ResizableHandle, ResizablePanel, ResizablePanelGroup, ScatterChart, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, Slider, Spinner, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, buildCartesianOption, buildHeatmapOption, buildHorizontalBarOption, buildPieOption, buildRadarOption, buttonGroupVariants, buttonVariants, cn, createChart, createTimeSeriesData, formatFileSize, formatLabel, getCompatibleChartTypes, inferChartType, isArrowTable, isDataProps, isQueryProps, navigationMenuTriggerStyle, normalizeChartData, normalizeHeatmapData, sortTimeSeriesAscending, toChartArray, toChartValue, toggleVariants, transformGenieData, truncateLabel, useAllThemeColors, useAnalyticsQuery, useChartData, useFormField, useGenieChat, usePluginClientConfig, usePortalContainer, useResolvedPortalContainer, useSidebar, useThemeColors };
@@ -1,4 +1,4 @@
1
- import "./plugin.js";
1
+ import { PluginClientConfigs, PluginEndpointMap, PluginEndpoints } from "./plugin.js";
2
2
  import "./cache.js";
3
3
  import "./execute.js";
4
4
  import { GenieAttachmentResponse, GenieMessageResponse, GenieStatementResponse, GenieStreamEvent } from "./genie.js";
@@ -1,2 +1,13 @@
1
1
  import "express";
2
- import "json-schema";
2
+ import "json-schema";
3
+
4
+ //#region ../shared/src/plugin.d.ts
5
+ /** Map of endpoint names to their full paths for a plugin */
6
+ type PluginEndpointMap = Record<string, string>;
7
+ /** Map of plugin names to their endpoint maps */
8
+ type PluginEndpoints = Record<string, PluginEndpointMap>;
9
+ /** Map of plugin names to their client-exposed config */
10
+ type PluginClientConfigs = Record<string, Record<string, unknown>>;
11
+ //#endregion
12
+ export { PluginClientConfigs, PluginEndpointMap, PluginEndpoints };
13
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","names":[],"sources":["../../../../shared/src/plugin.ts"],"mappings":";;;;;KAuNY,iBAAA,GAAoB,MAAA;;KAGpB,eAAA,GAAkB,MAAA,SAAe,iBAAA;;KAGjC,mBAAA,GAAsB,MAAA,SAAe,MAAA"}
@@ -251,7 +251,65 @@ A proxied plugin instance that executes as the user
251
251
 
252
252
  #### Throws[​](#throws "Direct link to Throws")
253
253
 
254
- AuthenticationError if user token is not available in request headers (production only). In development mode (`NODE_ENV=development`), falls back to the service principal instead of throwing.
254
+ AuthenticationError if user token is not available in request headers (production only). In development mode (`NODE_ENV=development`), skips user impersonation instead of throwing.
255
+
256
+ ***
257
+
258
+ ### clientConfig()[​](#clientconfig "Direct link to clientConfig()")
259
+
260
+ ```ts
261
+ clientConfig(): Record<string, unknown>;
262
+
263
+ ```
264
+
265
+ Returns startup config to expose to the client. Override this to surface server-side values that are safe to publish to the frontend, such as feature flags, resource IDs, or other app boot settings.
266
+
267
+ This runs once when the server starts, so it should not depend on request-scoped or user-specific state.
268
+
269
+ String values that match non-public environment variables are redacted unless you intentionally expose them via a matching `PUBLIC_APPKIT_` env var.
270
+
271
+ Values must be JSON-serializable plain data (no functions, Dates, classes, Maps, Sets, BigInts, or circular references). By default returns an empty object (plugin contributes nothing to client config).
272
+
273
+ On the client, read the config with the `usePluginClientConfig` hook (React) or the `getPluginClientConfig` function (vanilla JS), both from `@databricks/appkit-ui`.
274
+
275
+ #### Returns[​](#returns-3 "Direct link to Returns")
276
+
277
+ `Record`<`string`, `unknown`>
278
+
279
+ #### Example[​](#example "Direct link to Example")
280
+
281
+ ```ts
282
+ // Server — plugin definition
283
+ class MyPlugin extends Plugin<MyConfig> {
284
+ clientConfig() {
285
+ return {
286
+ warehouseId: this.config.warehouseId,
287
+ features: { darkMode: true },
288
+ };
289
+ }
290
+ }
291
+
292
+ // Client — React component
293
+ import { usePluginClientConfig } from "@databricks/appkit-ui/react";
294
+
295
+ interface MyPluginConfig { warehouseId: string; features: { darkMode: boolean } }
296
+
297
+ const config = usePluginClientConfig<MyPluginConfig>("myPlugin");
298
+ config.warehouseId; // "abc-123"
299
+
300
+ // Client — vanilla JS
301
+ import { getPluginClientConfig } from "@databricks/appkit-ui/js";
302
+
303
+ const config = getPluginClientConfig<MyPluginConfig>("myPlugin");
304
+
305
+ ```
306
+
307
+ #### Implementation of[​](#implementation-of-2 "Direct link to Implementation of")
308
+
309
+ ```ts
310
+ BasePlugin.clientConfig
311
+
312
+ ```
255
313
 
256
314
  ***
257
315
 
@@ -283,7 +341,7 @@ All errors are caught and `undefined` is returned (production-safe). Route handl
283
341
  | `options` | `PluginExecutionSettings` |
284
342
  | `userKey?` | `string` |
285
343
 
286
- #### Returns[​](#returns-3 "Direct link to Returns")
344
+ #### Returns[​](#returns-4 "Direct link to Returns")
287
345
 
288
346
  `Promise`<`T` | `undefined`>
289
347
 
@@ -315,7 +373,7 @@ userKey?: string): Promise<void>;
315
373
  | `options` | [`StreamExecutionSettings`](./docs/api/appkit/Interface.StreamExecutionSettings.md) |
316
374
  | `userKey?` | `string` |
317
375
 
318
- #### Returns[​](#returns-4 "Direct link to Returns")
376
+ #### Returns[​](#returns-5 "Direct link to Returns")
319
377
 
320
378
  `Promise`<`void`>
321
379
 
@@ -332,11 +390,11 @@ Returns the public exports for this plugin. Override this to define a custom pub
332
390
 
333
391
  The returned object becomes the plugin's public API on the AppKit instance (e.g. `appkit.myPlugin.method()`). AppKit automatically binds method context and adds `asUser(req)` for user-scoped execution.
334
392
 
335
- #### Returns[​](#returns-5 "Direct link to Returns")
393
+ #### Returns[​](#returns-6 "Direct link to Returns")
336
394
 
337
395
  `unknown`
338
396
 
339
- #### Example[​](#example "Direct link to Example")
397
+ #### Example[​](#example-1 "Direct link to Example")
340
398
 
341
399
  ```ts
342
400
  class MyPlugin extends Plugin {
@@ -353,7 +411,7 @@ appkit.myPlugin.getData();
353
411
 
354
412
  ```
355
413
 
356
- #### Implementation of[​](#implementation-of-2 "Direct link to Implementation of")
414
+ #### Implementation of[​](#implementation-of-3 "Direct link to Implementation of")
357
415
 
358
416
  ```ts
359
417
  BasePlugin.exports
@@ -369,11 +427,11 @@ getEndpoints(): PluginEndpointMap;
369
427
 
370
428
  ```
371
429
 
372
- #### Returns[​](#returns-6 "Direct link to Returns")
430
+ #### Returns[​](#returns-7 "Direct link to Returns")
373
431
 
374
432
  `PluginEndpointMap`
375
433
 
376
- #### Implementation of[​](#implementation-of-3 "Direct link to Implementation of")
434
+ #### Implementation of[​](#implementation-of-4 "Direct link to Implementation of")
377
435
 
378
436
  ```ts
379
437
  BasePlugin.getEndpoints
@@ -389,11 +447,11 @@ getSkipBodyParsingPaths(): ReadonlySet<string>;
389
447
 
390
448
  ```
391
449
 
392
- #### Returns[​](#returns-7 "Direct link to Returns")
450
+ #### Returns[​](#returns-8 "Direct link to Returns")
393
451
 
394
452
  `ReadonlySet`<`string`>
395
453
 
396
- #### Implementation of[​](#implementation-of-4 "Direct link to Implementation of")
454
+ #### Implementation of[​](#implementation-of-5 "Direct link to Implementation of")
397
455
 
398
456
  ```ts
399
457
  BasePlugin.getSkipBodyParsingPaths
@@ -415,11 +473,11 @@ injectRoutes(_: Router): void;
415
473
  | --------- | -------- |
416
474
  | `_` | `Router` |
417
475
 
418
- #### Returns[​](#returns-8 "Direct link to Returns")
476
+ #### Returns[​](#returns-9 "Direct link to Returns")
419
477
 
420
478
  `void`
421
479
 
422
- #### Implementation of[​](#implementation-of-5 "Direct link to Implementation of")
480
+ #### Implementation of[​](#implementation-of-6 "Direct link to Implementation of")
423
481
 
424
482
  ```ts
425
483
  BasePlugin.injectRoutes
@@ -442,7 +500,7 @@ protected registerEndpoint(name: string, path: string): void;
442
500
  | `name` | `string` |
443
501
  | `path` | `string` |
444
502
 
445
- #### Returns[​](#returns-9 "Direct link to Returns")
503
+ #### Returns[​](#returns-10 "Direct link to Returns")
446
504
 
447
505
  `void`
448
506
 
@@ -465,7 +523,7 @@ Returns the `x-forwarded-user` header when present. In development mode (`NODE_E
465
523
  | --------- | --------- |
466
524
  | `req` | `Request` |
467
525
 
468
- #### Returns[​](#returns-10 "Direct link to Returns")
526
+ #### Returns[​](#returns-11 "Direct link to Returns")
469
527
 
470
528
  `string`
471
529
 
@@ -495,7 +553,7 @@ protected route<_TResponse>(router: Router, config: RouteConfig): void;
495
553
  | `router` | `Router` |
496
554
  | `config` | `RouteConfig` |
497
555
 
498
- #### Returns[​](#returns-11 "Direct link to Returns")
556
+ #### Returns[​](#returns-12 "Direct link to Returns")
499
557
 
500
558
  `void`
501
559
 
@@ -508,11 +566,11 @@ setup(): Promise<void>;
508
566
 
509
567
  ```
510
568
 
511
- #### Returns[​](#returns-12 "Direct link to Returns")
569
+ #### Returns[​](#returns-13 "Direct link to Returns")
512
570
 
513
571
  `Promise`<`void`>
514
572
 
515
- #### Implementation of[​](#implementation-of-6 "Direct link to Implementation of")
573
+ #### Implementation of[​](#implementation-of-7 "Direct link to Implementation of")
516
574
 
517
575
  ```ts
518
576
  BasePlugin.setup