@prismiq/react 0.1.1 → 0.2.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 (49) hide show
  1. package/dist/{CustomSQLEditor-d84v_Cgp.d.cts → ChatBubble-ARocmvZD.d.cts} +40 -3
  2. package/dist/{CustomSQLEditor-CYlOtecq.d.ts → ChatBubble-BN_CjIpk.d.ts} +40 -3
  3. package/dist/{DashboardDialog-CZD8I-6z.d.cts → DashboardDialog-UhUGXx2h.d.ts} +5 -3
  4. package/dist/{DashboardDialog-DBNTVVSp.d.ts → DashboardDialog-Z-HypxmG.d.cts} +5 -3
  5. package/dist/charts/index.d.cts +2 -2
  6. package/dist/charts/index.d.ts +2 -2
  7. package/dist/{chunk-VQDFS6VS.cjs → chunk-FKXCINUF.cjs} +368 -210
  8. package/dist/chunk-FKXCINUF.cjs.map +1 -0
  9. package/dist/{chunk-ET7GCREP.js → chunk-GELI7MDZ.js} +482 -7
  10. package/dist/chunk-GELI7MDZ.js.map +1 -0
  11. package/dist/{chunk-WWTT2OJ5.js → chunk-HKZFEXT6.js} +27 -9
  12. package/dist/chunk-HKZFEXT6.js.map +1 -0
  13. package/dist/{chunk-3LDRRDJ6.js → chunk-JBJ5LEAG.js} +186 -28
  14. package/dist/chunk-JBJ5LEAG.js.map +1 -0
  15. package/dist/{chunk-URJH4H6G.cjs → chunk-PG7QBH3G.cjs} +485 -6
  16. package/dist/chunk-PG7QBH3G.cjs.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-B8DelfpL.d.cts} +1 -1
  32. package/dist/{index-DXGLs1yY.d.ts → index-RbfYPQD_.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 +74 -7
  36. package/dist/index.d.ts +74 -7
  37. package/dist/index.js +5 -5
  38. package/dist/index.js.map +1 -1
  39. package/dist/{types-j0kPJ9Hz.d.cts → types-ccB9Ps3k.d.cts} +44 -1
  40. package/dist/{types-j0kPJ9Hz.d.ts → types-ccB9Ps3k.d.ts} +44 -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,83 @@ 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
+ * @yields StreamChunk objects as the response is generated.
2863
+ */
2864
+ async *streamChat(message, history, currentSql, signal) {
2865
+ const url = `${this.endpoint}/llm/chat`;
2866
+ const headers = {
2867
+ "Content-Type": "application/json",
2868
+ "X-Tenant-ID": this.tenantId
2869
+ };
2870
+ if (this.userId) headers["X-User-ID"] = this.userId;
2871
+ if (this.schemaName) headers["X-Schema-Name"] = this.schemaName;
2872
+ if (this.customHeaders) Object.assign(headers, this.customHeaders);
2873
+ if (this.getToken) {
2874
+ const token = await this.getToken();
2875
+ headers["Authorization"] = `Bearer ${token}`;
2876
+ }
2877
+ const response = await fetch(url, {
2878
+ method: "POST",
2879
+ headers,
2880
+ body: JSON.stringify({
2881
+ message,
2882
+ history,
2883
+ current_sql: currentSql
2884
+ }),
2885
+ signal
2886
+ });
2887
+ if (!response.ok) {
2888
+ throw new PrismiqError(
2889
+ `LLM chat failed: ${response.status} ${response.statusText}`,
2890
+ response.status
2891
+ );
2892
+ }
2893
+ const reader = response.body?.getReader();
2894
+ if (!reader) return;
2895
+ const decoder = new TextDecoder();
2896
+ let buffer = "";
2897
+ try {
2898
+ while (true) {
2899
+ const { done, value } = await reader.read();
2900
+ if (done) break;
2901
+ buffer += decoder.decode(value, { stream: true });
2902
+ const lines = buffer.split("\n");
2903
+ buffer = lines.pop() ?? "";
2904
+ for (const line of lines) {
2905
+ if (line.startsWith("data: ")) {
2906
+ const data = line.slice(6).trim();
2907
+ if (data) {
2908
+ try {
2909
+ const chunk = JSON.parse(data);
2910
+ yield chunk;
2911
+ } catch {
2912
+ }
2913
+ }
2914
+ }
2915
+ }
2916
+ }
2917
+ } finally {
2918
+ reader.releaseLock();
2919
+ }
2920
+ }
2844
2921
  };
2845
2922
  var AnalyticsContext = createContext(null);
2846
2923
  var CallbacksContext = createContext({});
@@ -3898,6 +3975,140 @@ function CrossFilterProvider({
3898
3975
  function useCrossFilterOptional() {
3899
3976
  return useContext(CrossFilterContext);
3900
3977
  }
3978
+
3979
+ // src/hooks/useLLMStatus.ts
3980
+ function useLLMStatus() {
3981
+ const { client } = useAnalytics();
3982
+ const [status, setStatus] = useState({ enabled: false });
3983
+ const [isLoading, setIsLoading] = useState(true);
3984
+ const [error, setError] = useState(null);
3985
+ useEffect(() => {
3986
+ if (!client) {
3987
+ setIsLoading(false);
3988
+ return;
3989
+ }
3990
+ let cancelled = false;
3991
+ client.getLLMStatus().then((result) => {
3992
+ if (!cancelled) {
3993
+ setStatus(result);
3994
+ setError(null);
3995
+ }
3996
+ }).catch((err) => {
3997
+ if (!cancelled) {
3998
+ setStatus({ enabled: false });
3999
+ setError(err instanceof Error ? err : new Error(String(err)));
4000
+ }
4001
+ }).finally(() => {
4002
+ if (!cancelled) setIsLoading(false);
4003
+ });
4004
+ return () => {
4005
+ cancelled = true;
4006
+ };
4007
+ }, [client]);
4008
+ return {
4009
+ enabled: status.enabled,
4010
+ provider: status.provider,
4011
+ model: status.model,
4012
+ isLoading,
4013
+ error
4014
+ };
4015
+ }
4016
+ function useLLMChat() {
4017
+ const { client } = useAnalytics();
4018
+ const [messages, setMessages] = useState([]);
4019
+ const [isStreaming, setIsStreaming] = useState(false);
4020
+ const [streamingContent, setStreamingContent] = useState("");
4021
+ const [suggestedSql, setSuggestedSql] = useState(null);
4022
+ const [error, setError] = useState(null);
4023
+ const abortRef = useRef(null);
4024
+ const isStreamingRef = useRef(false);
4025
+ const messagesRef = useRef([]);
4026
+ messagesRef.current = messages;
4027
+ const sendMessage = useCallback(
4028
+ async (message, currentSql) => {
4029
+ if (!client || isStreamingRef.current) return;
4030
+ abortRef.current?.abort();
4031
+ const controller = new AbortController();
4032
+ abortRef.current = controller;
4033
+ const userMsg = { role: "user", content: message };
4034
+ setMessages((prev) => [...prev, userMsg]);
4035
+ isStreamingRef.current = true;
4036
+ setIsStreaming(true);
4037
+ setStreamingContent("");
4038
+ setSuggestedSql(null);
4039
+ setError(null);
4040
+ let accumulatedText = "";
4041
+ let lastSql = null;
4042
+ try {
4043
+ const history = messagesRef.current.map((m) => ({
4044
+ role: m.role,
4045
+ content: m.content
4046
+ }));
4047
+ for await (const chunk of client.streamChat(
4048
+ message,
4049
+ history,
4050
+ currentSql,
4051
+ controller.signal
4052
+ )) {
4053
+ if (controller.signal.aborted) break;
4054
+ switch (chunk.type) {
4055
+ case "text":
4056
+ accumulatedText += chunk.content ?? "";
4057
+ setStreamingContent(accumulatedText);
4058
+ break;
4059
+ case "sql":
4060
+ lastSql = chunk.content ?? null;
4061
+ setSuggestedSql(lastSql);
4062
+ break;
4063
+ case "error":
4064
+ setError(chunk.content ?? "Unknown error");
4065
+ break;
4066
+ case "done":
4067
+ break;
4068
+ }
4069
+ }
4070
+ } catch (err) {
4071
+ if (err instanceof Error && err.name !== "AbortError") {
4072
+ setError(err.message);
4073
+ }
4074
+ } finally {
4075
+ isStreamingRef.current = false;
4076
+ setIsStreaming(false);
4077
+ if (accumulatedText) {
4078
+ const assistantMsg = {
4079
+ role: "assistant",
4080
+ content: accumulatedText
4081
+ };
4082
+ setMessages((prev) => [...prev, assistantMsg]);
4083
+ }
4084
+ setStreamingContent("");
4085
+ }
4086
+ },
4087
+ [client]
4088
+ );
4089
+ useEffect(() => {
4090
+ return () => {
4091
+ abortRef.current?.abort();
4092
+ };
4093
+ }, []);
4094
+ const clearHistory = useCallback(() => {
4095
+ abortRef.current?.abort();
4096
+ setMessages([]);
4097
+ setStreamingContent("");
4098
+ setSuggestedSql(null);
4099
+ setError(null);
4100
+ setIsStreaming(false);
4101
+ }, []);
4102
+ return {
4103
+ messages,
4104
+ isStreaming,
4105
+ streamingContent,
4106
+ suggestedSql,
4107
+ sendMessage,
4108
+ clearHistory,
4109
+ error
4110
+ };
4111
+ }
3901
4112
  var nodeStyles = {
3902
4113
  display: "flex",
3903
4114
  alignItems: "center",
@@ -4360,6 +4571,7 @@ function SchemaExplorer({
4360
4571
  selectedColumns = [],
4361
4572
  searchable = true,
4362
4573
  collapsible = true,
4574
+ headerAction,
4363
4575
  className,
4364
4576
  style
4365
4577
  }) {
@@ -4395,10 +4607,12 @@ function SchemaExplorer({
4395
4607
  style: { ...containerStyles4, ...style },
4396
4608
  role: "tree",
4397
4609
  "aria-label": "Database schema",
4610
+ "data-testid": "schema-explorer-root",
4398
4611
  children: [
4399
4612
  /* @__PURE__ */ jsxs("div", { style: headerStyles2, children: [
4400
4613
  /* @__PURE__ */ jsx(Icon, { name: "table", size: 16, style: { color: "var(--prismiq-color-primary)" } }),
4401
- /* @__PURE__ */ jsx("span", { style: titleStyles2, children: "Schema Explorer" })
4614
+ /* @__PURE__ */ jsx("span", { style: { ...titleStyles2, flex: 1 }, children: "Schema Explorer" }),
4615
+ headerAction
4402
4616
  ] }),
4403
4617
  searchable && /* @__PURE__ */ jsx("div", { style: searchContainerStyles, children: /* @__PURE__ */ jsx(
4404
4618
  Input,
@@ -4407,7 +4621,8 @@ function SchemaExplorer({
4407
4621
  placeholder: "Search tables and columns...",
4408
4622
  value: searchQuery,
4409
4623
  onChange: (e) => setSearchQuery(e.target.value),
4410
- style: { width: "100%" }
4624
+ style: { width: "100%" },
4625
+ "data-testid": "schema-explorer-search"
4411
4626
  }
4412
4627
  ) }),
4413
4628
  /* @__PURE__ */ jsx("div", { style: treeContainerStyles, children: isLoading ? /* @__PURE__ */ jsx(LoadingSkeleton, {}) : error ? /* @__PURE__ */ jsxs("div", { style: errorStyles2, children: [
@@ -8871,7 +9086,14 @@ function CustomSQLEditor({
8871
9086
  style
8872
9087
  }) {
8873
9088
  const [sql, setSql] = useState(initialSql);
9089
+ const prevInitialSqlRef = useRef(initialSql);
8874
9090
  const [isFocused, setIsFocused] = useState(false);
9091
+ useEffect(() => {
9092
+ if (initialSql !== prevInitialSqlRef.current) {
9093
+ prevInitialSqlRef.current = initialSql;
9094
+ setSql(initialSql);
9095
+ }
9096
+ }, [initialSql]);
8875
9097
  const [executeEnabled, setExecuteEnabled] = useState(false);
8876
9098
  const [lastExecutedSql, setLastExecutedSql] = useState(null);
8877
9099
  const {
@@ -8917,7 +9139,7 @@ function CustomSQLEditor({
8917
9139
  ...buttonStyles,
8918
9140
  ...canExecute ? {} : buttonDisabledStyles
8919
9141
  };
8920
- return /* @__PURE__ */ jsxs("div", { className, style: { ...containerStyles17, ...style }, children: [
9142
+ return /* @__PURE__ */ jsxs("div", { className, style: { ...containerStyles17, ...style }, "data-testid": "custom-sql-editor", children: [
8921
9143
  /* @__PURE__ */ jsxs("div", { style: editorWrapperStyles, children: [
8922
9144
  /* @__PURE__ */ jsx(
8923
9145
  "textarea",
@@ -8931,7 +9153,8 @@ function CustomSQLEditor({
8931
9153
  style: mergedTextareaStyles,
8932
9154
  spellCheck: false,
8933
9155
  autoComplete: "off",
8934
- autoCapitalize: "off"
9156
+ autoCapitalize: "off",
9157
+ "data-testid": "custom-sql-textarea"
8935
9158
  }
8936
9159
  ),
8937
9160
  validation && !validation.valid && /* @__PURE__ */ jsxs("div", { style: validationErrorStyles, children: [
@@ -8955,6 +9178,7 @@ function CustomSQLEditor({
8955
9178
  disabled: !canExecute,
8956
9179
  style: mergedButtonStyles,
8957
9180
  type: "button",
9181
+ "data-testid": "custom-sql-run-button",
8958
9182
  children: [
8959
9183
  isLoading ? "Executing..." : "Run Query",
8960
9184
  /* @__PURE__ */ jsx("span", { style: { fontSize: "11px", opacity: 0.7 }, children: "(Cmd+Enter)" })
@@ -9156,7 +9380,258 @@ function TableSelector({
9156
9380
  ] })
9157
9381
  ] });
9158
9382
  }
9383
+ function parseContent(content) {
9384
+ const startToken = "```sql";
9385
+ const endToken = "```";
9386
+ const parts = [];
9387
+ let cursor = 0;
9388
+ while (cursor < content.length) {
9389
+ const start = content.indexOf(startToken, cursor);
9390
+ if (start === -1) break;
9391
+ if (start > cursor) {
9392
+ parts.push({ type: "text", value: content.slice(cursor, start) });
9393
+ }
9394
+ const sqlStart = content.indexOf("\n", start + startToken.length);
9395
+ if (sqlStart === -1) break;
9396
+ const end = content.indexOf(endToken, sqlStart + 1);
9397
+ if (end === -1) break;
9398
+ const sql = content.slice(sqlStart + 1, end).trim();
9399
+ if (sql) {
9400
+ parts.push({ type: "sql", value: sql });
9401
+ }
9402
+ cursor = end + endToken.length;
9403
+ }
9404
+ if (cursor < content.length) {
9405
+ parts.push({ type: "text", value: content.slice(cursor) });
9406
+ }
9407
+ return parts;
9408
+ }
9409
+ function ChatBubble({ message, onApplySql }) {
9410
+ const { theme } = useTheme();
9411
+ const isUser = message.role === "user";
9412
+ const parts = useMemo(() => parseContent(message.content), [message.content]);
9413
+ const bubbleStyle = {
9414
+ maxWidth: "85%",
9415
+ padding: `${theme.spacing.sm} ${theme.spacing.md}`,
9416
+ borderRadius: theme.radius.md,
9417
+ fontSize: theme.fontSizes.sm,
9418
+ lineHeight: 1.5,
9419
+ whiteSpace: "pre-wrap",
9420
+ wordBreak: "break-word",
9421
+ alignSelf: isUser ? "flex-end" : "flex-start",
9422
+ backgroundColor: isUser ? theme.colors.primary : theme.colors.surface,
9423
+ color: isUser ? "#fff" : theme.colors.text,
9424
+ border: isUser ? "none" : `1px solid ${theme.colors.border}`
9425
+ };
9426
+ const sqlBlockStyle = {
9427
+ backgroundColor: isUser ? "rgba(0,0,0,0.2)" : theme.colors.background,
9428
+ borderRadius: theme.radius.sm,
9429
+ padding: theme.spacing.sm,
9430
+ margin: `${theme.spacing.xs} 0`,
9431
+ fontFamily: theme.fonts.mono,
9432
+ fontSize: theme.fontSizes.xs,
9433
+ overflow: "auto",
9434
+ position: "relative"
9435
+ };
9436
+ const applyBtnContainerStyle = {
9437
+ display: "flex",
9438
+ justifyContent: "flex-end",
9439
+ marginTop: theme.spacing.xs
9440
+ };
9441
+ return /* @__PURE__ */ jsx("div", { style: bubbleStyle, children: parts.map((part, i) => {
9442
+ if (part.type === "sql") {
9443
+ return /* @__PURE__ */ jsxs("div", { "data-testid": `chat-sql-${i}`, children: [
9444
+ /* @__PURE__ */ jsx("pre", { style: sqlBlockStyle, children: /* @__PURE__ */ jsx("code", { children: part.value }) }),
9445
+ onApplySql && /* @__PURE__ */ jsx("div", { style: applyBtnContainerStyle, children: /* @__PURE__ */ jsx(
9446
+ Button,
9447
+ {
9448
+ variant: "ghost",
9449
+ size: "sm",
9450
+ onClick: () => onApplySql(part.value),
9451
+ "data-testid": `apply-sql-btn-${i}`,
9452
+ children: "Apply to Editor"
9453
+ }
9454
+ ) })
9455
+ ] }, i);
9456
+ }
9457
+ return /* @__PURE__ */ jsx("span", { children: part.value }, i);
9458
+ }) });
9459
+ }
9460
+ function ChatPanel({ currentSql, onApplySql }) {
9461
+ const { theme } = useTheme();
9462
+ const {
9463
+ messages,
9464
+ isStreaming,
9465
+ streamingContent,
9466
+ suggestedSql,
9467
+ sendMessage,
9468
+ clearHistory,
9469
+ error
9470
+ } = useLLMChat();
9471
+ const [input, setInput] = useState("");
9472
+ const messagesEndRef = useRef(null);
9473
+ useEffect(() => {
9474
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
9475
+ }, [messages, streamingContent]);
9476
+ const handleSend = useCallback(() => {
9477
+ const trimmed = input.trim();
9478
+ if (!trimmed) return;
9479
+ setInput("");
9480
+ void sendMessage(trimmed, currentSql);
9481
+ }, [input, currentSql, sendMessage]);
9482
+ const handleKeyDown = useCallback(
9483
+ (e) => {
9484
+ if (e.key === "Enter" && !e.shiftKey) {
9485
+ e.preventDefault();
9486
+ handleSend();
9487
+ }
9488
+ },
9489
+ [handleSend]
9490
+ );
9491
+ const containerStyle = {
9492
+ display: "flex",
9493
+ flexDirection: "column",
9494
+ height: "100%",
9495
+ borderLeft: `1px solid ${theme.colors.border}`,
9496
+ backgroundColor: theme.colors.background
9497
+ };
9498
+ const headerStyle = {
9499
+ display: "flex",
9500
+ alignItems: "center",
9501
+ justifyContent: "space-between",
9502
+ padding: `${theme.spacing.sm} ${theme.spacing.md}`,
9503
+ borderBottom: `1px solid ${theme.colors.border}`,
9504
+ flexShrink: 0
9505
+ };
9506
+ const headerTitleStyle = {
9507
+ display: "flex",
9508
+ alignItems: "center",
9509
+ gap: theme.spacing.xs,
9510
+ fontSize: theme.fontSizes.sm,
9511
+ fontWeight: 600,
9512
+ color: theme.colors.text
9513
+ };
9514
+ const messagesStyle = {
9515
+ flex: 1,
9516
+ overflow: "auto",
9517
+ padding: theme.spacing.md,
9518
+ display: "flex",
9519
+ flexDirection: "column",
9520
+ gap: theme.spacing.md
9521
+ };
9522
+ const streamingStyle = {
9523
+ alignSelf: "flex-start",
9524
+ maxWidth: "85%",
9525
+ padding: `${theme.spacing.sm} ${theme.spacing.md}`,
9526
+ borderRadius: theme.radius.md,
9527
+ fontSize: theme.fontSizes.sm,
9528
+ lineHeight: 1.5,
9529
+ whiteSpace: "pre-wrap",
9530
+ wordBreak: "break-word",
9531
+ backgroundColor: theme.colors.surface,
9532
+ color: theme.colors.text,
9533
+ border: `1px solid ${theme.colors.border}`
9534
+ };
9535
+ const suggestedSqlStyle = {
9536
+ padding: theme.spacing.sm,
9537
+ borderTop: `1px solid ${theme.colors.border}`,
9538
+ display: "flex",
9539
+ alignItems: "center",
9540
+ justifyContent: "center",
9541
+ flexShrink: 0
9542
+ };
9543
+ const inputAreaStyle = {
9544
+ display: "flex",
9545
+ gap: theme.spacing.sm,
9546
+ padding: theme.spacing.sm,
9547
+ borderTop: `1px solid ${theme.colors.border}`,
9548
+ flexShrink: 0
9549
+ };
9550
+ const textareaStyle = {
9551
+ flex: 1,
9552
+ resize: "none",
9553
+ border: `1px solid ${theme.colors.border}`,
9554
+ borderRadius: theme.radius.sm,
9555
+ padding: theme.spacing.sm,
9556
+ fontSize: theme.fontSizes.sm,
9557
+ fontFamily: theme.fonts.sans,
9558
+ backgroundColor: theme.colors.surface,
9559
+ color: theme.colors.text,
9560
+ outline: "none",
9561
+ minHeight: "36px",
9562
+ maxHeight: "120px"
9563
+ };
9564
+ const emptyStyle = {
9565
+ flex: 1,
9566
+ display: "flex",
9567
+ alignItems: "center",
9568
+ justifyContent: "center",
9569
+ textAlign: "center",
9570
+ padding: theme.spacing.lg,
9571
+ color: theme.colors.textMuted,
9572
+ fontSize: theme.fontSizes.sm
9573
+ };
9574
+ const errorStyle = {
9575
+ padding: theme.spacing.sm,
9576
+ margin: `0 ${theme.spacing.md}`,
9577
+ borderRadius: theme.radius.sm,
9578
+ backgroundColor: "rgba(239, 68, 68, 0.1)",
9579
+ color: "#ef4444",
9580
+ fontSize: theme.fontSizes.xs,
9581
+ flexShrink: 0
9582
+ };
9583
+ return /* @__PURE__ */ jsxs("div", { style: containerStyle, className: "prismiq-chat-panel", "data-testid": "chat-panel-root", children: [
9584
+ /* @__PURE__ */ jsxs("div", { style: headerStyle, children: [
9585
+ /* @__PURE__ */ jsxs("div", { style: headerTitleStyle, children: [
9586
+ /* @__PURE__ */ jsx(Icon, { name: "edit", size: 16 }),
9587
+ /* @__PURE__ */ jsx("span", { children: "SQL Assistant" })
9588
+ ] }),
9589
+ messages.length > 0 && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: clearHistory, "data-testid": "chat-clear", children: "Clear" })
9590
+ ] }),
9591
+ messages.length === 0 && !isStreaming ? /* @__PURE__ */ jsxs("div", { style: emptyStyle, "data-testid": "chat-empty", children: [
9592
+ "Ask me to help write SQL queries.",
9593
+ "\n",
9594
+ "I can see your database schema and validate queries."
9595
+ ] }) : /* @__PURE__ */ jsxs("div", { style: messagesStyle, "data-testid": "chat-messages", children: [
9596
+ messages.map((msg, i) => /* @__PURE__ */ jsx(ChatBubble, { message: msg, onApplySql }, i)),
9597
+ isStreaming && streamingContent && /* @__PURE__ */ jsxs("div", { style: streamingStyle, "data-testid": "chat-streaming", children: [
9598
+ streamingContent,
9599
+ "\u258D"
9600
+ ] }),
9601
+ isStreaming && !streamingContent && /* @__PURE__ */ jsx("div", { style: streamingStyle, "data-testid": "chat-streaming", children: "Thinking..." }),
9602
+ /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
9603
+ ] }),
9604
+ error && /* @__PURE__ */ jsx("div", { style: errorStyle, "data-testid": "chat-error", children: error }),
9605
+ 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" }) }),
9606
+ /* @__PURE__ */ jsxs("div", { style: inputAreaStyle, children: [
9607
+ /* @__PURE__ */ jsx(
9608
+ "textarea",
9609
+ {
9610
+ style: textareaStyle,
9611
+ value: input,
9612
+ onChange: (e) => setInput(e.target.value),
9613
+ onKeyDown: handleKeyDown,
9614
+ placeholder: "Ask about your data...",
9615
+ rows: 1,
9616
+ disabled: isStreaming,
9617
+ "data-testid": "chat-input"
9618
+ }
9619
+ ),
9620
+ /* @__PURE__ */ jsx(
9621
+ Button,
9622
+ {
9623
+ variant: "primary",
9624
+ size: "sm",
9625
+ onClick: handleSend,
9626
+ disabled: isStreaming || !input.trim(),
9627
+ "data-testid": "chat-send",
9628
+ children: /* @__PURE__ */ jsx(Icon, { name: "play", size: 16 })
9629
+ }
9630
+ )
9631
+ ] })
9632
+ ] });
9633
+ }
9159
9634
 
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
9635
+ 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 };
9636
+ //# sourceMappingURL=chunk-GELI7MDZ.js.map
9637
+ //# sourceMappingURL=chunk-GELI7MDZ.js.map