@prismiq/react 0.1.1 → 0.2.1

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 (49) hide show
  1. package/dist/{CustomSQLEditor-CYlOtecq.d.ts → ChatBubble-3mFpV7yX.d.ts} +42 -3
  2. package/dist/{CustomSQLEditor-d84v_Cgp.d.cts → ChatBubble-CMkEupzn.d.cts} +42 -3
  3. package/dist/{DashboardDialog-DBNTVVSp.d.ts → DashboardDialog-DMmZ3bnf.d.cts} +5 -3
  4. package/dist/{DashboardDialog-CZD8I-6z.d.cts → DashboardDialog-RlcPkdMt.d.ts} +5 -3
  5. package/dist/charts/index.d.cts +2 -2
  6. package/dist/charts/index.d.ts +2 -2
  7. package/dist/{chunk-3LDRRDJ6.js → chunk-F6QYNQEW.js} +194 -28
  8. package/dist/chunk-F6QYNQEW.js.map +1 -0
  9. package/dist/{chunk-WWTT2OJ5.js → chunk-HKZFEXT6.js} +27 -9
  10. package/dist/chunk-HKZFEXT6.js.map +1 -0
  11. package/dist/{chunk-VQDFS6VS.cjs → chunk-N6I3QOHG.cjs} +376 -210
  12. package/dist/chunk-N6I3QOHG.cjs.map +1 -0
  13. package/dist/{chunk-URJH4H6G.cjs → chunk-NXXKG4GN.cjs} +520 -6
  14. package/dist/chunk-NXXKG4GN.cjs.map +1 -0
  15. package/dist/{chunk-ET7GCREP.js → chunk-VEFYFB5H.js} +517 -7
  16. package/dist/chunk-VEFYFB5H.js.map +1 -0
  17. package/dist/{chunk-MDXGGZSW.cjs → chunk-ZYVN6XAZ.cjs} +35 -37
  18. package/dist/chunk-ZYVN6XAZ.cjs.map +1 -0
  19. package/dist/components/index.cjs +62 -54
  20. package/dist/components/index.d.cts +2 -2
  21. package/dist/components/index.d.ts +2 -2
  22. package/dist/components/index.js +1 -1
  23. package/dist/dashboard/index.cjs +34 -34
  24. package/dist/dashboard/index.d.cts +7 -5
  25. package/dist/dashboard/index.d.ts +7 -5
  26. package/dist/dashboard/index.js +2 -2
  27. package/dist/export/index.cjs +7 -7
  28. package/dist/export/index.d.cts +6 -4
  29. package/dist/export/index.d.ts +6 -4
  30. package/dist/export/index.js +1 -1
  31. package/dist/{index-CvKj3SWO.d.cts → index-BA2VUhgN.d.cts} +1 -1
  32. package/dist/{index-DXGLs1yY.d.ts → index-BPo89ZAj.d.ts} +1 -1
  33. package/dist/index.cjs +119 -103
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +77 -7
  36. package/dist/index.d.ts +77 -7
  37. package/dist/index.js +5 -5
  38. package/dist/index.js.map +1 -1
  39. package/dist/{types-j0kPJ9Hz.d.cts → types-BaI6sSAG.d.cts} +62 -1
  40. package/dist/{types-j0kPJ9Hz.d.ts → types-BaI6sSAG.d.ts} +62 -1
  41. package/dist/utils/index.d.cts +1 -1
  42. package/dist/utils/index.d.ts +1 -1
  43. package/package.json +2 -6
  44. package/dist/chunk-3LDRRDJ6.js.map +0 -1
  45. package/dist/chunk-ET7GCREP.js.map +0 -1
  46. package/dist/chunk-MDXGGZSW.cjs.map +0 -1
  47. package/dist/chunk-URJH4H6G.cjs.map +0 -1
  48. package/dist/chunk-VQDFS6VS.cjs.map +0 -1
  49. package/dist/chunk-WWTT2OJ5.js.map +0 -1
@@ -2843,6 +2843,88 @@ var PrismiqClient = class {
2843
2843
  })
2844
2844
  });
2845
2845
  }
2846
+ // ============================================================================
2847
+ // LLM Methods
2848
+ // ============================================================================
2849
+ /**
2850
+ * Get the LLM agent status.
2851
+ *
2852
+ * @returns LLM status including enabled state, provider, and model.
2853
+ */
2854
+ async getLLMStatus() {
2855
+ return this.request("/llm/status");
2856
+ }
2857
+ /**
2858
+ * Stream a chat response from the LLM agent via SSE.
2859
+ *
2860
+ * @param message - User's message.
2861
+ * @param history - Previous conversation messages.
2862
+ * @param currentSql - Current SQL in the editor (for context).
2863
+ * @param signal - Optional AbortSignal for cancellation.
2864
+ * @param widgetContext - Optional widget context for targeted SQL generation.
2865
+ * @yields StreamChunk objects as the response is generated.
2866
+ */
2867
+ async *streamChat(message, history, currentSql, signal, widgetContext) {
2868
+ const url = `${this.endpoint}/llm/chat`;
2869
+ const headers = {
2870
+ "Content-Type": "application/json",
2871
+ "X-Tenant-ID": this.tenantId
2872
+ };
2873
+ if (this.userId) headers["X-User-ID"] = this.userId;
2874
+ if (this.schemaName) headers["X-Schema-Name"] = this.schemaName;
2875
+ if (this.customHeaders) Object.assign(headers, this.customHeaders);
2876
+ if (this.getToken) {
2877
+ const token = await this.getToken();
2878
+ headers["Authorization"] = `Bearer ${token}`;
2879
+ }
2880
+ const body = {
2881
+ message,
2882
+ history,
2883
+ current_sql: currentSql
2884
+ };
2885
+ if (widgetContext) {
2886
+ body.widget_context = widgetContext;
2887
+ }
2888
+ const response = await fetch(url, {
2889
+ method: "POST",
2890
+ headers,
2891
+ body: JSON.stringify(body),
2892
+ signal
2893
+ });
2894
+ if (!response.ok) {
2895
+ throw new PrismiqError(
2896
+ `LLM chat failed: ${response.status} ${response.statusText}`,
2897
+ response.status
2898
+ );
2899
+ }
2900
+ const reader = response.body?.getReader();
2901
+ if (!reader) return;
2902
+ const decoder = new TextDecoder();
2903
+ let buffer = "";
2904
+ try {
2905
+ while (true) {
2906
+ const { done, value } = await reader.read();
2907
+ if (done) break;
2908
+ buffer += decoder.decode(value, { stream: true });
2909
+ const lines = buffer.split("\n");
2910
+ buffer = lines.pop() ?? "";
2911
+ for (const line of lines) {
2912
+ if (line.startsWith("data: ")) {
2913
+ const data = line.slice(6).trim();
2914
+ if (data) {
2915
+ try {
2916
+ const chunk = JSON.parse(data);
2917
+ yield chunk;
2918
+ } catch {
2919
+ }
2920
+ }
2921
+ }
2922
+ }
2923
+ }
2924
+ } finally {
2925
+ reader.releaseLock();
2926
+ }
2927
+ }
2846
2928
  };
2847
2929
  var AnalyticsContext = react.createContext(null);
2848
2930
  var CallbacksContext = react.createContext({});
@@ -3900,6 +3982,151 @@ function CrossFilterProvider({
3900
3982
  function useCrossFilterOptional() {
3901
3983
  return react.useContext(CrossFilterContext);
3902
3984
  }
3985
+
3986
+ // src/hooks/useLLMStatus.ts
3987
+ function useLLMStatus() {
3988
+ const { client } = useAnalytics();
3989
+ const [status, setStatus] = react.useState({ enabled: false });
3990
+ const [isLoading, setIsLoading] = react.useState(true);
3991
+ const [error, setError] = react.useState(null);
3992
+ react.useEffect(() => {
3993
+ if (!client) {
3994
+ setIsLoading(false);
3995
+ return;
3996
+ }
3997
+ let cancelled = false;
3998
+ client.getLLMStatus().then((result) => {
3999
+ if (!cancelled) {
4000
+ setStatus(result);
4001
+ setError(null);
4002
+ }
4003
+ }).catch((err) => {
4004
+ if (!cancelled) {
4005
+ setStatus({ enabled: false });
4006
+ setError(err instanceof Error ? err : new Error(String(err)));
4007
+ }
4008
+ }).finally(() => {
4009
+ if (!cancelled) setIsLoading(false);
4010
+ });
4011
+ return () => {
4012
+ cancelled = true;
4013
+ };
4014
+ }, [client]);
4015
+ return {
4016
+ enabled: status.enabled,
4017
+ provider: status.provider,
4018
+ model: status.model,
4019
+ isLoading,
4020
+ error
4021
+ };
4022
+ }
4023
+ function useLLMChat() {
4024
+ const { client } = useAnalytics();
4025
+ const [messages, setMessages] = react.useState([]);
4026
+ const [isStreaming, setIsStreaming] = react.useState(false);
4027
+ const [streamingContent, setStreamingContent] = react.useState("");
4028
+ const [suggestedSql, setSuggestedSql] = react.useState(null);
4029
+ const [error, setError] = react.useState(null);
4030
+ const [statusMessage, setStatusMessage] = react.useState(null);
4031
+ const abortRef = react.useRef(null);
4032
+ const isStreamingRef = react.useRef(false);
4033
+ const messagesRef = react.useRef([]);
4034
+ messagesRef.current = messages;
4035
+ const sendMessage = react.useCallback(
4036
+ async (message, currentSql, widgetContext) => {
4037
+ if (!client || isStreamingRef.current) return;
4038
+ abortRef.current?.abort();
4039
+ const controller = new AbortController();
4040
+ abortRef.current = controller;
4041
+ const userMsg = { role: "user", content: message };
4042
+ setMessages((prev) => [...prev, userMsg]);
4043
+ isStreamingRef.current = true;
4044
+ setIsStreaming(true);
4045
+ setStreamingContent("");
4046
+ setSuggestedSql(null);
4047
+ setError(null);
4048
+ setStatusMessage(null);
4049
+ let accumulatedText = "";
4050
+ let lastSql = null;
4051
+ try {
4052
+ const history = messagesRef.current.map((m) => ({
4053
+ role: m.role,
4054
+ content: m.content
4055
+ }));
4056
+ for await (const chunk of client.streamChat(
4057
+ message,
4058
+ history,
4059
+ currentSql,
4060
+ controller.signal,
4061
+ widgetContext
4062
+ )) {
4063
+ if (controller.signal.aborted) break;
4064
+ switch (chunk.type) {
4065
+ case "text":
4066
+ accumulatedText += chunk.content ?? "";
4067
+ setStreamingContent(accumulatedText);
4068
+ setStatusMessage(null);
4069
+ break;
4070
+ case "sql":
4071
+ lastSql = chunk.content ?? null;
4072
+ setSuggestedSql(lastSql);
4073
+ break;
4074
+ case "status":
4075
+ setStatusMessage(chunk.content ?? null);
4076
+ break;
4077
+ case "error":
4078
+ setError(chunk.content ?? "Unknown error");
4079
+ break;
4080
+ case "done":
4081
+ setStatusMessage(null);
4082
+ break;
4083
+ }
4084
+ }
4085
+ } catch (err) {
4086
+ if (err instanceof Error && err.name !== "AbortError") {
4087
+ setError(err.message);
4088
+ }
4089
+ } finally {
4090
+ isStreamingRef.current = false;
4091
+ setIsStreaming(false);
4092
+ setStatusMessage(null);
4093
+ if (accumulatedText) {
4094
+ const assistantMsg = {
4095
+ role: "assistant",
4096
+ content: accumulatedText
4097
+ };
4098
+ setMessages((prev) => [...prev, assistantMsg]);
4099
+ }
4100
+ setStreamingContent("");
4101
+ }
4102
+ },
4103
+ [client]
4104
+ );
4105
+ react.useEffect(() => {
4106
+ return () => {
4107
+ abortRef.current?.abort();
4108
+ };
4109
+ }, []);
4110
+ const clearHistory = react.useCallback(() => {
4111
+ abortRef.current?.abort();
4112
+ setMessages([]);
4113
+ setStreamingContent("");
4114
+ setSuggestedSql(null);
4115
+ setError(null);
4116
+ setIsStreaming(false);
4117
+ setStatusMessage(null);
4118
+ }, []);
4119
+ return {
4120
+ messages,
4121
+ isStreaming,
4122
+ streamingContent,
4123
+ suggestedSql,
4124
+ sendMessage,
4125
+ clearHistory,
4126
+ error,
4127
+ statusMessage
4128
+ };
4129
+ }
3903
4130
  var nodeStyles = {
3904
4131
  display: "flex",
3905
4132
  alignItems: "center",
@@ -4362,6 +4589,7 @@ function SchemaExplorer({
4362
4589
  selectedColumns = [],
4363
4590
  searchable = true,
4364
4591
  collapsible = true,
4592
+ headerAction,
4365
4593
  className,
4366
4594
  style
4367
4595
  }) {
@@ -4397,10 +4625,12 @@ function SchemaExplorer({
4397
4625
  style: { ...containerStyles4, ...style },
4398
4626
  role: "tree",
4399
4627
  "aria-label": "Database schema",
4628
+ "data-testid": "schema-explorer-root",
4400
4629
  children: [
4401
4630
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: headerStyles2, children: [
4402
4631
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "table", size: 16, style: { color: "var(--prismiq-color-primary)" } }),
4403
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: titleStyles2, children: "Schema Explorer" })
4632
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...titleStyles2, flex: 1 }, children: "Schema Explorer" }),
4633
+ headerAction
4404
4634
  ] }),
4405
4635
  searchable && /* @__PURE__ */ jsxRuntime.jsx("div", { style: searchContainerStyles, children: /* @__PURE__ */ jsxRuntime.jsx(
4406
4636
  Input,
@@ -4409,7 +4639,8 @@ function SchemaExplorer({
4409
4639
  placeholder: "Search tables and columns...",
4410
4640
  value: searchQuery,
4411
4641
  onChange: (e) => setSearchQuery(e.target.value),
4412
- style: { width: "100%" }
4642
+ style: { width: "100%" },
4643
+ "data-testid": "schema-explorer-search"
4413
4644
  }
4414
4645
  ) }),
4415
4646
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: treeContainerStyles, children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(LoadingSkeleton, {}) : error ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: errorStyles2, children: [
@@ -8873,7 +9104,14 @@ function CustomSQLEditor({
8873
9104
  style
8874
9105
  }) {
8875
9106
  const [sql, setSql] = react.useState(initialSql);
9107
+ const prevInitialSqlRef = react.useRef(initialSql);
8876
9108
  const [isFocused, setIsFocused] = react.useState(false);
9109
+ react.useEffect(() => {
9110
+ if (initialSql !== prevInitialSqlRef.current) {
9111
+ prevInitialSqlRef.current = initialSql;
9112
+ setSql(initialSql);
9113
+ }
9114
+ }, [initialSql]);
8877
9115
  const [executeEnabled, setExecuteEnabled] = react.useState(false);
8878
9116
  const [lastExecutedSql, setLastExecutedSql] = react.useState(null);
8879
9117
  const {
@@ -8919,7 +9157,7 @@ function CustomSQLEditor({
8919
9157
  ...buttonStyles,
8920
9158
  ...canExecute ? {} : buttonDisabledStyles
8921
9159
  };
8922
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: { ...containerStyles17, ...style }, children: [
9160
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: { ...containerStyles17, ...style }, "data-testid": "custom-sql-editor", children: [
8923
9161
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: editorWrapperStyles, children: [
8924
9162
  /* @__PURE__ */ jsxRuntime.jsx(
8925
9163
  "textarea",
@@ -8933,7 +9171,8 @@ function CustomSQLEditor({
8933
9171
  style: mergedTextareaStyles,
8934
9172
  spellCheck: false,
8935
9173
  autoComplete: "off",
8936
- autoCapitalize: "off"
9174
+ autoCapitalize: "off",
9175
+ "data-testid": "custom-sql-textarea"
8937
9176
  }
8938
9177
  ),
8939
9178
  validation && !validation.valid && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: validationErrorStyles, children: [
@@ -8957,6 +9196,7 @@ function CustomSQLEditor({
8957
9196
  disabled: !canExecute,
8958
9197
  style: mergedButtonStyles,
8959
9198
  type: "button",
9199
+ "data-testid": "custom-sql-run-button",
8960
9200
  children: [
8961
9201
  isLoading ? "Executing..." : "Run Query",
8962
9202
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "11px", opacity: 0.7 }, children: "(Cmd+Enter)" })
@@ -9158,6 +9398,276 @@ function TableSelector({
9158
9398
  ] })
9159
9399
  ] });
9160
9400
  }
9401
+ function parseContent(content) {
9402
+ const startToken = "```sql";
9403
+ const endToken = "```";
9404
+ const parts = [];
9405
+ let cursor = 0;
9406
+ while (cursor < content.length) {
9407
+ const start = content.indexOf(startToken, cursor);
9408
+ if (start === -1) break;
9409
+ if (start > cursor) {
9410
+ parts.push({ type: "text", value: content.slice(cursor, start) });
9411
+ }
9412
+ const sqlStart = content.indexOf("\n", start + startToken.length);
9413
+ if (sqlStart === -1) break;
9414
+ const end = content.indexOf(endToken, sqlStart + 1);
9415
+ if (end === -1) break;
9416
+ const sql = content.slice(sqlStart + 1, end).trim();
9417
+ if (sql) {
9418
+ parts.push({ type: "sql", value: sql });
9419
+ }
9420
+ cursor = end + endToken.length;
9421
+ }
9422
+ if (cursor < content.length) {
9423
+ parts.push({ type: "text", value: content.slice(cursor) });
9424
+ }
9425
+ return parts;
9426
+ }
9427
+ function ChatBubble({ message, onApplySql }) {
9428
+ const { theme } = chunkLMTG3LRC_cjs.useTheme();
9429
+ const isUser = message.role === "user";
9430
+ const parts = react.useMemo(() => parseContent(message.content), [message.content]);
9431
+ const bubbleStyle = {
9432
+ maxWidth: "85%",
9433
+ padding: `${theme.spacing.sm} ${theme.spacing.md}`,
9434
+ borderRadius: theme.radius.md,
9435
+ fontSize: theme.fontSizes.sm,
9436
+ lineHeight: 1.5,
9437
+ whiteSpace: "pre-wrap",
9438
+ wordBreak: "break-word",
9439
+ alignSelf: isUser ? "flex-end" : "flex-start",
9440
+ backgroundColor: isUser ? theme.colors.primary : theme.colors.surface,
9441
+ color: isUser ? "#fff" : theme.colors.text,
9442
+ border: isUser ? "none" : `1px solid ${theme.colors.border}`
9443
+ };
9444
+ const sqlBlockStyle = {
9445
+ backgroundColor: isUser ? "rgba(0,0,0,0.2)" : theme.colors.background,
9446
+ borderRadius: theme.radius.sm,
9447
+ padding: theme.spacing.sm,
9448
+ margin: `${theme.spacing.xs} 0`,
9449
+ fontFamily: theme.fonts.mono,
9450
+ fontSize: theme.fontSizes.xs,
9451
+ overflow: "auto",
9452
+ position: "relative"
9453
+ };
9454
+ const applyBtnContainerStyle = {
9455
+ display: "flex",
9456
+ justifyContent: "flex-end",
9457
+ marginTop: theme.spacing.xs
9458
+ };
9459
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: bubbleStyle, children: parts.map((part, i) => {
9460
+ if (part.type === "sql") {
9461
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": `chat-sql-${i}`, children: [
9462
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { style: sqlBlockStyle, children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: part.value }) }),
9463
+ onApplySql && /* @__PURE__ */ jsxRuntime.jsx("div", { style: applyBtnContainerStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
9464
+ Button,
9465
+ {
9466
+ variant: "ghost",
9467
+ size: "sm",
9468
+ onClick: () => onApplySql(part.value),
9469
+ "data-testid": `apply-sql-btn-${i}`,
9470
+ children: "Apply to Editor"
9471
+ }
9472
+ ) })
9473
+ ] }, i);
9474
+ }
9475
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { children: part.value }, i);
9476
+ }) });
9477
+ }
9478
+ function ChatPanel({ currentSql, onApplySql, widgetContext }) {
9479
+ const { theme } = chunkLMTG3LRC_cjs.useTheme();
9480
+ const {
9481
+ messages,
9482
+ isStreaming,
9483
+ streamingContent,
9484
+ suggestedSql,
9485
+ sendMessage,
9486
+ clearHistory,
9487
+ error,
9488
+ statusMessage
9489
+ } = useLLMChat();
9490
+ const [input, setInput] = react.useState("");
9491
+ const messagesEndRef = react.useRef(null);
9492
+ react.useEffect(() => {
9493
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
9494
+ }, [messages, streamingContent]);
9495
+ const handleSend = react.useCallback(() => {
9496
+ const trimmed = input.trim();
9497
+ if (!trimmed) return;
9498
+ setInput("");
9499
+ void sendMessage(trimmed, currentSql, widgetContext);
9500
+ }, [input, currentSql, widgetContext, sendMessage]);
9501
+ const handleKeyDown = react.useCallback(
9502
+ (e) => {
9503
+ if (e.key === "Enter" && !e.shiftKey) {
9504
+ e.preventDefault();
9505
+ handleSend();
9506
+ }
9507
+ },
9508
+ [handleSend]
9509
+ );
9510
+ const containerStyle = {
9511
+ display: "flex",
9512
+ flexDirection: "column",
9513
+ height: "100%",
9514
+ borderLeft: `1px solid ${theme.colors.border}`,
9515
+ backgroundColor: theme.colors.background
9516
+ };
9517
+ const headerStyle = {
9518
+ display: "flex",
9519
+ alignItems: "center",
9520
+ justifyContent: "space-between",
9521
+ padding: `${theme.spacing.sm} ${theme.spacing.md}`,
9522
+ borderBottom: `1px solid ${theme.colors.border}`,
9523
+ flexShrink: 0
9524
+ };
9525
+ const headerTitleStyle = {
9526
+ display: "flex",
9527
+ alignItems: "center",
9528
+ gap: theme.spacing.xs,
9529
+ fontSize: theme.fontSizes.sm,
9530
+ fontWeight: 600,
9531
+ color: theme.colors.text
9532
+ };
9533
+ const messagesStyle = {
9534
+ flex: 1,
9535
+ overflow: "auto",
9536
+ padding: theme.spacing.md,
9537
+ display: "flex",
9538
+ flexDirection: "column",
9539
+ gap: theme.spacing.md
9540
+ };
9541
+ const streamingStyle = {
9542
+ alignSelf: "flex-start",
9543
+ maxWidth: "85%",
9544
+ padding: `${theme.spacing.sm} ${theme.spacing.md}`,
9545
+ borderRadius: theme.radius.md,
9546
+ fontSize: theme.fontSizes.sm,
9547
+ lineHeight: 1.5,
9548
+ whiteSpace: "pre-wrap",
9549
+ wordBreak: "break-word",
9550
+ backgroundColor: theme.colors.surface,
9551
+ color: theme.colors.text,
9552
+ border: `1px solid ${theme.colors.border}`
9553
+ };
9554
+ const suggestedSqlStyle = {
9555
+ padding: theme.spacing.sm,
9556
+ borderTop: `1px solid ${theme.colors.border}`,
9557
+ display: "flex",
9558
+ alignItems: "center",
9559
+ justifyContent: "center",
9560
+ flexShrink: 0
9561
+ };
9562
+ const inputAreaStyle = {
9563
+ display: "flex",
9564
+ gap: theme.spacing.sm,
9565
+ padding: theme.spacing.sm,
9566
+ borderTop: `1px solid ${theme.colors.border}`,
9567
+ flexShrink: 0
9568
+ };
9569
+ const textareaStyle = {
9570
+ flex: 1,
9571
+ resize: "none",
9572
+ border: `1px solid ${theme.colors.border}`,
9573
+ borderRadius: theme.radius.sm,
9574
+ padding: theme.spacing.sm,
9575
+ fontSize: theme.fontSizes.sm,
9576
+ fontFamily: theme.fonts.sans,
9577
+ backgroundColor: theme.colors.surface,
9578
+ color: theme.colors.text,
9579
+ outline: "none",
9580
+ minHeight: "36px",
9581
+ maxHeight: "120px"
9582
+ };
9583
+ const emptyStyle = {
9584
+ flex: 1,
9585
+ display: "flex",
9586
+ alignItems: "center",
9587
+ justifyContent: "center",
9588
+ textAlign: "center",
9589
+ padding: theme.spacing.lg,
9590
+ color: theme.colors.textMuted,
9591
+ fontSize: theme.fontSizes.sm
9592
+ };
9593
+ const errorStyle = {
9594
+ padding: theme.spacing.sm,
9595
+ margin: `0 ${theme.spacing.md}`,
9596
+ borderRadius: theme.radius.sm,
9597
+ backgroundColor: "rgba(239, 68, 68, 0.1)",
9598
+ color: "#ef4444",
9599
+ fontSize: theme.fontSizes.xs,
9600
+ flexShrink: 0
9601
+ };
9602
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: containerStyle, className: "prismiq-chat-panel", "data-testid": "chat-panel-root", children: [
9603
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: headerStyle, children: [
9604
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: headerTitleStyle, children: [
9605
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "edit", size: 16 }),
9606
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "SQL Assistant" })
9607
+ ] }),
9608
+ messages.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", onClick: clearHistory, "data-testid": "chat-clear", children: "Clear" })
9609
+ ] }),
9610
+ messages.length === 0 && !isStreaming ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: emptyStyle, "data-testid": "chat-empty", children: [
9611
+ "Ask me to help write SQL queries.",
9612
+ "\n",
9613
+ "I can see your database schema and validate queries."
9614
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: messagesStyle, "data-testid": "chat-messages", children: [
9615
+ messages.map((msg, i) => /* @__PURE__ */ jsxRuntime.jsx(ChatBubble, { message: msg, onApplySql }, i)),
9616
+ isStreaming && streamingContent && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: streamingStyle, "data-testid": "chat-streaming", children: [
9617
+ streamingContent,
9618
+ "\u258D"
9619
+ ] }),
9620
+ isStreaming && !streamingContent && /* @__PURE__ */ jsxRuntime.jsx("div", { style: streamingStyle, "data-testid": "chat-streaming", children: "Thinking..." }),
9621
+ isStreaming && statusMessage && /* @__PURE__ */ jsxRuntime.jsxs(
9622
+ "div",
9623
+ {
9624
+ style: {
9625
+ display: "flex",
9626
+ alignItems: "center",
9627
+ gap: theme.spacing.xs,
9628
+ fontSize: theme.fontSizes.xs,
9629
+ color: theme.colors.textMuted,
9630
+ padding: `${theme.spacing.xs} 0`
9631
+ },
9632
+ "data-testid": "chat-status",
9633
+ children: [
9634
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "sync", size: 12 }),
9635
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: statusMessage })
9636
+ ]
9637
+ }
9638
+ ),
9639
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: messagesEndRef })
9640
+ ] }),
9641
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: errorStyle, "data-testid": "chat-error", children: error }),
9642
+ suggestedSql && !isStreaming && /* @__PURE__ */ jsxRuntime.jsx("div", { style: suggestedSqlStyle, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", size: "sm", onClick: () => onApplySql(suggestedSql), "data-testid": "chat-apply-sql", children: "Apply SQL to Editor" }) }),
9643
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: inputAreaStyle, children: [
9644
+ /* @__PURE__ */ jsxRuntime.jsx(
9645
+ "textarea",
9646
+ {
9647
+ style: textareaStyle,
9648
+ value: input,
9649
+ onChange: (e) => setInput(e.target.value),
9650
+ onKeyDown: handleKeyDown,
9651
+ placeholder: "Ask about your data...",
9652
+ rows: 1,
9653
+ disabled: isStreaming,
9654
+ "data-testid": "chat-input"
9655
+ }
9656
+ ),
9657
+ /* @__PURE__ */ jsxRuntime.jsx(
9658
+ Button,
9659
+ {
9660
+ variant: "primary",
9661
+ size: "sm",
9662
+ onClick: handleSend,
9663
+ disabled: isStreaming || !input.trim(),
9664
+ "data-testid": "chat-send",
9665
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "play", size: 16 })
9666
+ }
9667
+ )
9668
+ ] })
9669
+ ] });
9670
+ }
9161
9671
 
9162
9672
  exports.AggregationPicker = AggregationPicker;
9163
9673
  exports.AnalyticsProvider = AnalyticsProvider;
@@ -9165,6 +9675,8 @@ exports.AutoSaveIndicator = AutoSaveIndicator;
9165
9675
  exports.Badge = Badge;
9166
9676
  exports.Button = Button;
9167
9677
  exports.CalculatedFieldBuilder = CalculatedFieldBuilder;
9678
+ exports.ChatBubble = ChatBubble;
9679
+ exports.ChatPanel = ChatPanel;
9168
9680
  exports.Checkbox = Checkbox;
9169
9681
  exports.CollapsibleSection = CollapsibleSection;
9170
9682
  exports.ColorPaletteSelector = ColorPaletteSelector;
@@ -9228,10 +9740,12 @@ exports.useDashboardMutations = useDashboardMutations;
9228
9740
  exports.useDashboardPinStatus = useDashboardPinStatus;
9229
9741
  exports.useDashboards = useDashboards;
9230
9742
  exports.useDebouncedLayoutSave = useDebouncedLayoutSave;
9743
+ exports.useLLMChat = useLLMChat;
9744
+ exports.useLLMStatus = useLLMStatus;
9231
9745
  exports.usePinMutations = usePinMutations;
9232
9746
  exports.usePinnedDashboards = usePinnedDashboards;
9233
9747
  exports.useQuery = useQuery;
9234
9748
  exports.useSavedQueries = useSavedQueries;
9235
9749
  exports.useSchema = useSchema;
9236
- //# sourceMappingURL=chunk-URJH4H6G.cjs.map
9237
- //# sourceMappingURL=chunk-URJH4H6G.cjs.map
9750
+ //# sourceMappingURL=chunk-NXXKG4GN.cjs.map
9751
+ //# sourceMappingURL=chunk-NXXKG4GN.cjs.map