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