@prismiq/react 0.1.0 → 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.
- package/dist/{CustomSQLEditor-DYeId0Gp.d.ts → ChatBubble-ARocmvZD.d.cts} +48 -4
- package/dist/{CustomSQLEditor-BXB4rf1q.d.cts → ChatBubble-BN_CjIpk.d.ts} +48 -4
- package/dist/{DashboardDialog-B3vYC5Gs.d.ts → DashboardDialog-UhUGXx2h.d.ts} +6 -4
- package/dist/{DashboardDialog-LHmrtNQU.d.cts → DashboardDialog-Z-HypxmG.d.cts} +6 -4
- package/dist/{accessibility-2yy5yqRR.d.cts → accessibility-Bu2mNtaB.d.cts} +1 -1
- package/dist/{accessibility-2yy5yqRR.d.ts → accessibility-Bu2mNtaB.d.ts} +1 -1
- package/dist/charts/index.cjs +27 -27
- package/dist/charts/index.d.cts +2 -2
- package/dist/charts/index.d.ts +2 -2
- package/dist/charts/index.js +2 -2
- package/dist/{chunk-NK7HKX2J.cjs → chunk-73TPDGXB.cjs} +7 -7
- package/dist/{chunk-NK7HKX2J.cjs.map → chunk-73TPDGXB.cjs.map} +1 -1
- package/dist/{chunk-FEABEF3J.cjs → chunk-FKXCINUF.cjs} +551 -299
- package/dist/chunk-FKXCINUF.cjs.map +1 -0
- package/dist/{chunk-2H5WTH4K.js → chunk-FQ23KG6G.js} +3 -3
- package/dist/{chunk-2H5WTH4K.js.map → chunk-FQ23KG6G.js.map} +1 -1
- package/dist/{chunk-UPYINBZU.js → chunk-GELI7MDZ.js} +982 -51
- package/dist/chunk-GELI7MDZ.js.map +1 -0
- package/dist/{chunk-WWTT2OJ5.js → chunk-HKZFEXT6.js} +27 -9
- package/dist/chunk-HKZFEXT6.js.map +1 -0
- package/dist/{chunk-MOAEEF5P.js → chunk-JBJ5LEAG.js} +362 -110
- package/dist/chunk-JBJ5LEAG.js.map +1 -0
- package/dist/{chunk-4AVL6GQK.cjs → chunk-KXB2IZI2.cjs} +36 -9
- package/dist/chunk-KXB2IZI2.cjs.map +1 -0
- package/dist/{chunk-EX74SI67.js → chunk-LBE6GIBC.js} +36 -9
- package/dist/chunk-LBE6GIBC.js.map +1 -0
- package/dist/{chunk-NY6TZLST.cjs → chunk-PG7QBH3G.cjs} +988 -53
- package/dist/chunk-PG7QBH3G.cjs.map +1 -0
- package/dist/{chunk-MDXGGZSW.cjs → chunk-ZYVN6XAZ.cjs} +35 -37
- package/dist/chunk-ZYVN6XAZ.cjs.map +1 -0
- package/dist/components/index.cjs +63 -55
- package/dist/components/index.d.cts +2 -2
- package/dist/components/index.d.ts +2 -2
- package/dist/components/index.js +2 -2
- package/dist/dashboard/index.cjs +36 -36
- package/dist/dashboard/index.d.cts +7 -5
- package/dist/dashboard/index.d.ts +7 -5
- package/dist/dashboard/index.js +4 -4
- package/dist/export/index.cjs +7 -7
- package/dist/export/index.d.cts +6 -4
- package/dist/export/index.d.ts +6 -4
- package/dist/export/index.js +1 -1
- package/dist/{index-C-Qcuu4Y.d.cts → index-B8DelfpL.d.cts} +2 -2
- package/dist/{index-rPc7ijt8.d.ts → index-RbfYPQD_.d.ts} +2 -2
- package/dist/index.cjs +150 -134
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -9
- package/dist/index.d.ts +97 -9
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/{types-WrCbOeAV.d.cts → types-ccB9Ps3k.d.cts} +59 -1
- package/dist/{types-WrCbOeAV.d.ts → types-ccB9Ps3k.d.ts} +59 -1
- package/dist/utils/index.cjs +15 -15
- package/dist/utils/index.d.cts +5 -21
- package/dist/utils/index.d.ts +5 -21
- package/dist/utils/index.js +1 -1
- package/package.json +3 -7
- package/dist/chunk-4AVL6GQK.cjs.map +0 -1
- package/dist/chunk-EX74SI67.js.map +0 -1
- package/dist/chunk-FEABEF3J.cjs.map +0 -1
- package/dist/chunk-MDXGGZSW.cjs.map +0 -1
- package/dist/chunk-MOAEEF5P.js.map +0 -1
- package/dist/chunk-NY6TZLST.cjs.map +0 -1
- package/dist/chunk-UPYINBZU.js.map +0 -1
- package/dist/chunk-WWTT2OJ5.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chunkLMTG3LRC_cjs = require('./chunk-LMTG3LRC.cjs');
|
|
4
|
-
var
|
|
4
|
+
var chunkKXB2IZI2_cjs = require('./chunk-KXB2IZI2.cjs');
|
|
5
5
|
var react = require('react');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
7
|
var reactDom = require('react-dom');
|
|
@@ -1179,7 +1179,7 @@ var Dialog = react.forwardRef(function Dialog2({
|
|
|
1179
1179
|
style,
|
|
1180
1180
|
...props
|
|
1181
1181
|
}, _ref) {
|
|
1182
|
-
const { containerRef } =
|
|
1182
|
+
const { containerRef } = chunkKXB2IZI2_cjs.useFocusTrap({
|
|
1183
1183
|
active: open,
|
|
1184
1184
|
onEscape: closeOnEscape ? onClose : void 0
|
|
1185
1185
|
});
|
|
@@ -2327,6 +2327,9 @@ var PrismiqClient = class {
|
|
|
2327
2327
|
}
|
|
2328
2328
|
/**
|
|
2329
2329
|
* Make an authenticated request to the API.
|
|
2330
|
+
*
|
|
2331
|
+
* @param path - API path (starting with /)
|
|
2332
|
+
* @param options - Fetch options including signal for cancellation
|
|
2330
2333
|
*/
|
|
2331
2334
|
async request(path, options = {}) {
|
|
2332
2335
|
const url = `${this.endpoint}${path}`;
|
|
@@ -2472,6 +2475,17 @@ var PrismiqClient = class {
|
|
|
2472
2475
|
const result = await this.request(path);
|
|
2473
2476
|
return result.values;
|
|
2474
2477
|
}
|
|
2478
|
+
/**
|
|
2479
|
+
* Get data source metadata including display names and descriptions.
|
|
2480
|
+
*
|
|
2481
|
+
* Returns metadata for all exposed tables/views that can be used
|
|
2482
|
+
* to show user-friendly names in the UI instead of raw table names.
|
|
2483
|
+
*
|
|
2484
|
+
* @returns Array of data source metadata.
|
|
2485
|
+
*/
|
|
2486
|
+
async getDataSources() {
|
|
2487
|
+
return this.request("/data-sources");
|
|
2488
|
+
}
|
|
2475
2489
|
// ============================================================================
|
|
2476
2490
|
// Query Methods
|
|
2477
2491
|
// ============================================================================
|
|
@@ -2505,12 +2519,14 @@ var PrismiqClient = class {
|
|
|
2505
2519
|
*
|
|
2506
2520
|
* @param query - The query definition to execute.
|
|
2507
2521
|
* @param bypassCache - If true, bypass cache and re-execute query.
|
|
2522
|
+
* @param signal - Optional AbortSignal for cancellation.
|
|
2508
2523
|
* @returns The query result with all rows and cache metadata.
|
|
2509
2524
|
*/
|
|
2510
|
-
async executeQuery(query, bypassCache = false) {
|
|
2525
|
+
async executeQuery(query, bypassCache = false, signal) {
|
|
2511
2526
|
return this.request("/query/execute", {
|
|
2512
2527
|
method: "POST",
|
|
2513
|
-
body: JSON.stringify({ query, bypass_cache: bypassCache })
|
|
2528
|
+
body: JSON.stringify({ query, bypass_cache: bypassCache }),
|
|
2529
|
+
signal
|
|
2514
2530
|
});
|
|
2515
2531
|
}
|
|
2516
2532
|
/**
|
|
@@ -2827,6 +2843,83 @@ var PrismiqClient = class {
|
|
|
2827
2843
|
})
|
|
2828
2844
|
});
|
|
2829
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
|
+
* @yields StreamChunk objects as the response is generated.
|
|
2865
|
+
*/
|
|
2866
|
+
async *streamChat(message, history, currentSql, signal) {
|
|
2867
|
+
const url = `${this.endpoint}/llm/chat`;
|
|
2868
|
+
const headers = {
|
|
2869
|
+
"Content-Type": "application/json",
|
|
2870
|
+
"X-Tenant-ID": this.tenantId
|
|
2871
|
+
};
|
|
2872
|
+
if (this.userId) headers["X-User-ID"] = this.userId;
|
|
2873
|
+
if (this.schemaName) headers["X-Schema-Name"] = this.schemaName;
|
|
2874
|
+
if (this.customHeaders) Object.assign(headers, this.customHeaders);
|
|
2875
|
+
if (this.getToken) {
|
|
2876
|
+
const token = await this.getToken();
|
|
2877
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
2878
|
+
}
|
|
2879
|
+
const response = await fetch(url, {
|
|
2880
|
+
method: "POST",
|
|
2881
|
+
headers,
|
|
2882
|
+
body: JSON.stringify({
|
|
2883
|
+
message,
|
|
2884
|
+
history,
|
|
2885
|
+
current_sql: currentSql
|
|
2886
|
+
}),
|
|
2887
|
+
signal
|
|
2888
|
+
});
|
|
2889
|
+
if (!response.ok) {
|
|
2890
|
+
throw new PrismiqError(
|
|
2891
|
+
`LLM chat failed: ${response.status} ${response.statusText}`,
|
|
2892
|
+
response.status
|
|
2893
|
+
);
|
|
2894
|
+
}
|
|
2895
|
+
const reader = response.body?.getReader();
|
|
2896
|
+
if (!reader) return;
|
|
2897
|
+
const decoder = new TextDecoder();
|
|
2898
|
+
let buffer = "";
|
|
2899
|
+
try {
|
|
2900
|
+
while (true) {
|
|
2901
|
+
const { done, value } = await reader.read();
|
|
2902
|
+
if (done) break;
|
|
2903
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2904
|
+
const lines = buffer.split("\n");
|
|
2905
|
+
buffer = lines.pop() ?? "";
|
|
2906
|
+
for (const line of lines) {
|
|
2907
|
+
if (line.startsWith("data: ")) {
|
|
2908
|
+
const data = line.slice(6).trim();
|
|
2909
|
+
if (data) {
|
|
2910
|
+
try {
|
|
2911
|
+
const chunk = JSON.parse(data);
|
|
2912
|
+
yield chunk;
|
|
2913
|
+
} catch {
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
} finally {
|
|
2920
|
+
reader.releaseLock();
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2830
2923
|
};
|
|
2831
2924
|
var AnalyticsContext = react.createContext(null);
|
|
2832
2925
|
var CallbacksContext = react.createContext({});
|
|
@@ -2863,6 +2956,7 @@ function AnalyticsProvider({
|
|
|
2863
2956
|
}
|
|
2864
2957
|
const client = clientRef.current;
|
|
2865
2958
|
const [schema, setSchema] = react.useState(null);
|
|
2959
|
+
const [dataSources, setDataSources] = react.useState([]);
|
|
2866
2960
|
const [isLoading, setIsLoading] = react.useState(true);
|
|
2867
2961
|
const [error, setError] = react.useState(null);
|
|
2868
2962
|
const hasFetchedSchemaRef = react.useRef(false);
|
|
@@ -2880,8 +2974,13 @@ function AnalyticsProvider({
|
|
|
2880
2974
|
setIsLoading(true);
|
|
2881
2975
|
setError(null);
|
|
2882
2976
|
try {
|
|
2883
|
-
const fetchedSchema = await
|
|
2977
|
+
const [fetchedSchema, fetchedDataSources] = await Promise.all([
|
|
2978
|
+
client.getSchema(),
|
|
2979
|
+
client.getDataSources().catch(() => [])
|
|
2980
|
+
// Non-critical, fallback to empty
|
|
2981
|
+
]);
|
|
2884
2982
|
setSchema(fetchedSchema);
|
|
2983
|
+
setDataSources(fetchedDataSources);
|
|
2885
2984
|
onSchemaLoadRef.current?.(fetchedSchema);
|
|
2886
2985
|
} catch (err) {
|
|
2887
2986
|
const schemaError = err instanceof Error ? err : new Error(String(err));
|
|
@@ -2903,6 +3002,7 @@ function AnalyticsProvider({
|
|
|
2903
3002
|
() => ({
|
|
2904
3003
|
client,
|
|
2905
3004
|
schema,
|
|
3005
|
+
dataSources,
|
|
2906
3006
|
isLoading,
|
|
2907
3007
|
error,
|
|
2908
3008
|
refetchSchema,
|
|
@@ -2910,7 +3010,7 @@ function AnalyticsProvider({
|
|
|
2910
3010
|
userId,
|
|
2911
3011
|
schemaName
|
|
2912
3012
|
}),
|
|
2913
|
-
[client, schema, isLoading, error, refetchSchema, tenantId, userId, schemaName]
|
|
3013
|
+
[client, schema, dataSources, isLoading, error, refetchSchema, tenantId, userId, schemaName]
|
|
2914
3014
|
);
|
|
2915
3015
|
const callbacks = react.useMemo(
|
|
2916
3016
|
() => ({
|
|
@@ -2931,22 +3031,44 @@ function useAnalytics() {
|
|
|
2931
3031
|
return context;
|
|
2932
3032
|
}
|
|
2933
3033
|
function useSchema() {
|
|
2934
|
-
const { schema, isLoading, error } = useAnalytics();
|
|
3034
|
+
const { schema, dataSources, isLoading, error } = useAnalytics();
|
|
2935
3035
|
const tables = react.useMemo(() => schema?.tables ?? [], [schema]);
|
|
2936
3036
|
const relationships = react.useMemo(() => schema?.relationships ?? [], [schema]);
|
|
3037
|
+
const dataSourceMap = react.useMemo(() => {
|
|
3038
|
+
const map = /* @__PURE__ */ new Map();
|
|
3039
|
+
for (const ds of dataSources) {
|
|
3040
|
+
map.set(ds.table, ds);
|
|
3041
|
+
}
|
|
3042
|
+
return map;
|
|
3043
|
+
}, [dataSources]);
|
|
2937
3044
|
const getTable = react.useCallback(
|
|
2938
3045
|
(name) => {
|
|
2939
3046
|
return tables.find((table) => table.name === name);
|
|
2940
3047
|
},
|
|
2941
3048
|
[tables]
|
|
2942
3049
|
);
|
|
3050
|
+
const getDisplayName = react.useCallback(
|
|
3051
|
+
(tableName) => {
|
|
3052
|
+
return dataSourceMap.get(tableName)?.title ?? tableName;
|
|
3053
|
+
},
|
|
3054
|
+
[dataSourceMap]
|
|
3055
|
+
);
|
|
3056
|
+
const getDescription = react.useCallback(
|
|
3057
|
+
(tableName) => {
|
|
3058
|
+
return dataSourceMap.get(tableName)?.subtitle ?? "";
|
|
3059
|
+
},
|
|
3060
|
+
[dataSourceMap]
|
|
3061
|
+
);
|
|
2943
3062
|
return {
|
|
2944
3063
|
schema,
|
|
2945
3064
|
tables,
|
|
2946
3065
|
relationships,
|
|
3066
|
+
dataSources,
|
|
2947
3067
|
isLoading,
|
|
2948
3068
|
error,
|
|
2949
|
-
getTable
|
|
3069
|
+
getTable,
|
|
3070
|
+
getDisplayName,
|
|
3071
|
+
getDescription
|
|
2950
3072
|
};
|
|
2951
3073
|
}
|
|
2952
3074
|
function queryEquals(a, b) {
|
|
@@ -3855,6 +3977,140 @@ function CrossFilterProvider({
|
|
|
3855
3977
|
function useCrossFilterOptional() {
|
|
3856
3978
|
return react.useContext(CrossFilterContext);
|
|
3857
3979
|
}
|
|
3980
|
+
|
|
3981
|
+
// src/hooks/useLLMStatus.ts
|
|
3982
|
+
function useLLMStatus() {
|
|
3983
|
+
const { client } = useAnalytics();
|
|
3984
|
+
const [status, setStatus] = react.useState({ enabled: false });
|
|
3985
|
+
const [isLoading, setIsLoading] = react.useState(true);
|
|
3986
|
+
const [error, setError] = react.useState(null);
|
|
3987
|
+
react.useEffect(() => {
|
|
3988
|
+
if (!client) {
|
|
3989
|
+
setIsLoading(false);
|
|
3990
|
+
return;
|
|
3991
|
+
}
|
|
3992
|
+
let cancelled = false;
|
|
3993
|
+
client.getLLMStatus().then((result) => {
|
|
3994
|
+
if (!cancelled) {
|
|
3995
|
+
setStatus(result);
|
|
3996
|
+
setError(null);
|
|
3997
|
+
}
|
|
3998
|
+
}).catch((err) => {
|
|
3999
|
+
if (!cancelled) {
|
|
4000
|
+
setStatus({ enabled: false });
|
|
4001
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
4002
|
+
}
|
|
4003
|
+
}).finally(() => {
|
|
4004
|
+
if (!cancelled) setIsLoading(false);
|
|
4005
|
+
});
|
|
4006
|
+
return () => {
|
|
4007
|
+
cancelled = true;
|
|
4008
|
+
};
|
|
4009
|
+
}, [client]);
|
|
4010
|
+
return {
|
|
4011
|
+
enabled: status.enabled,
|
|
4012
|
+
provider: status.provider,
|
|
4013
|
+
model: status.model,
|
|
4014
|
+
isLoading,
|
|
4015
|
+
error
|
|
4016
|
+
};
|
|
4017
|
+
}
|
|
4018
|
+
function useLLMChat() {
|
|
4019
|
+
const { client } = useAnalytics();
|
|
4020
|
+
const [messages, setMessages] = react.useState([]);
|
|
4021
|
+
const [isStreaming, setIsStreaming] = react.useState(false);
|
|
4022
|
+
const [streamingContent, setStreamingContent] = react.useState("");
|
|
4023
|
+
const [suggestedSql, setSuggestedSql] = react.useState(null);
|
|
4024
|
+
const [error, setError] = react.useState(null);
|
|
4025
|
+
const abortRef = react.useRef(null);
|
|
4026
|
+
const isStreamingRef = react.useRef(false);
|
|
4027
|
+
const messagesRef = react.useRef([]);
|
|
4028
|
+
messagesRef.current = messages;
|
|
4029
|
+
const sendMessage = react.useCallback(
|
|
4030
|
+
async (message, currentSql) => {
|
|
4031
|
+
if (!client || isStreamingRef.current) return;
|
|
4032
|
+
abortRef.current?.abort();
|
|
4033
|
+
const controller = new AbortController();
|
|
4034
|
+
abortRef.current = controller;
|
|
4035
|
+
const userMsg = { role: "user", content: message };
|
|
4036
|
+
setMessages((prev) => [...prev, userMsg]);
|
|
4037
|
+
isStreamingRef.current = true;
|
|
4038
|
+
setIsStreaming(true);
|
|
4039
|
+
setStreamingContent("");
|
|
4040
|
+
setSuggestedSql(null);
|
|
4041
|
+
setError(null);
|
|
4042
|
+
let accumulatedText = "";
|
|
4043
|
+
let lastSql = null;
|
|
4044
|
+
try {
|
|
4045
|
+
const history = messagesRef.current.map((m) => ({
|
|
4046
|
+
role: m.role,
|
|
4047
|
+
content: m.content
|
|
4048
|
+
}));
|
|
4049
|
+
for await (const chunk of client.streamChat(
|
|
4050
|
+
message,
|
|
4051
|
+
history,
|
|
4052
|
+
currentSql,
|
|
4053
|
+
controller.signal
|
|
4054
|
+
)) {
|
|
4055
|
+
if (controller.signal.aborted) break;
|
|
4056
|
+
switch (chunk.type) {
|
|
4057
|
+
case "text":
|
|
4058
|
+
accumulatedText += chunk.content ?? "";
|
|
4059
|
+
setStreamingContent(accumulatedText);
|
|
4060
|
+
break;
|
|
4061
|
+
case "sql":
|
|
4062
|
+
lastSql = chunk.content ?? null;
|
|
4063
|
+
setSuggestedSql(lastSql);
|
|
4064
|
+
break;
|
|
4065
|
+
case "error":
|
|
4066
|
+
setError(chunk.content ?? "Unknown error");
|
|
4067
|
+
break;
|
|
4068
|
+
case "done":
|
|
4069
|
+
break;
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4072
|
+
} catch (err) {
|
|
4073
|
+
if (err instanceof Error && err.name !== "AbortError") {
|
|
4074
|
+
setError(err.message);
|
|
4075
|
+
}
|
|
4076
|
+
} finally {
|
|
4077
|
+
isStreamingRef.current = false;
|
|
4078
|
+
setIsStreaming(false);
|
|
4079
|
+
if (accumulatedText) {
|
|
4080
|
+
const assistantMsg = {
|
|
4081
|
+
role: "assistant",
|
|
4082
|
+
content: accumulatedText
|
|
4083
|
+
};
|
|
4084
|
+
setMessages((prev) => [...prev, assistantMsg]);
|
|
4085
|
+
}
|
|
4086
|
+
setStreamingContent("");
|
|
4087
|
+
}
|
|
4088
|
+
},
|
|
4089
|
+
[client]
|
|
4090
|
+
);
|
|
4091
|
+
react.useEffect(() => {
|
|
4092
|
+
return () => {
|
|
4093
|
+
abortRef.current?.abort();
|
|
4094
|
+
};
|
|
4095
|
+
}, []);
|
|
4096
|
+
const clearHistory = react.useCallback(() => {
|
|
4097
|
+
abortRef.current?.abort();
|
|
4098
|
+
setMessages([]);
|
|
4099
|
+
setStreamingContent("");
|
|
4100
|
+
setSuggestedSql(null);
|
|
4101
|
+
setError(null);
|
|
4102
|
+
setIsStreaming(false);
|
|
4103
|
+
}, []);
|
|
4104
|
+
return {
|
|
4105
|
+
messages,
|
|
4106
|
+
isStreaming,
|
|
4107
|
+
streamingContent,
|
|
4108
|
+
suggestedSql,
|
|
4109
|
+
sendMessage,
|
|
4110
|
+
clearHistory,
|
|
4111
|
+
error
|
|
4112
|
+
};
|
|
4113
|
+
}
|
|
3858
4114
|
var nodeStyles = {
|
|
3859
4115
|
display: "flex",
|
|
3860
4116
|
alignItems: "center",
|
|
@@ -4317,6 +4573,7 @@ function SchemaExplorer({
|
|
|
4317
4573
|
selectedColumns = [],
|
|
4318
4574
|
searchable = true,
|
|
4319
4575
|
collapsible = true,
|
|
4576
|
+
headerAction,
|
|
4320
4577
|
className,
|
|
4321
4578
|
style
|
|
4322
4579
|
}) {
|
|
@@ -4352,10 +4609,12 @@ function SchemaExplorer({
|
|
|
4352
4609
|
style: { ...containerStyles4, ...style },
|
|
4353
4610
|
role: "tree",
|
|
4354
4611
|
"aria-label": "Database schema",
|
|
4612
|
+
"data-testid": "schema-explorer-root",
|
|
4355
4613
|
children: [
|
|
4356
4614
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: headerStyles2, children: [
|
|
4357
4615
|
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "table", size: 16, style: { color: "var(--prismiq-color-primary)" } }),
|
|
4358
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: titleStyles2, children: "Schema Explorer" })
|
|
4616
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...titleStyles2, flex: 1 }, children: "Schema Explorer" }),
|
|
4617
|
+
headerAction
|
|
4359
4618
|
] }),
|
|
4360
4619
|
searchable && /* @__PURE__ */ jsxRuntime.jsx("div", { style: searchContainerStyles, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4361
4620
|
Input,
|
|
@@ -4364,7 +4623,8 @@ function SchemaExplorer({
|
|
|
4364
4623
|
placeholder: "Search tables and columns...",
|
|
4365
4624
|
value: searchQuery,
|
|
4366
4625
|
onChange: (e) => setSearchQuery(e.target.value),
|
|
4367
|
-
style: { width: "100%" }
|
|
4626
|
+
style: { width: "100%" },
|
|
4627
|
+
"data-testid": "schema-explorer-search"
|
|
4368
4628
|
}
|
|
4369
4629
|
) }),
|
|
4370
4630
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: treeContainerStyles, children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(LoadingSkeleton, {}) : error ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: errorStyles2, children: [
|
|
@@ -4835,6 +5095,29 @@ var containerStyles7 = {
|
|
|
4835
5095
|
gap: "var(--prismiq-spacing-xs)",
|
|
4836
5096
|
flex: 1
|
|
4837
5097
|
};
|
|
5098
|
+
var comboboxContainerStyles = {
|
|
5099
|
+
position: "relative",
|
|
5100
|
+
flex: 1
|
|
5101
|
+
};
|
|
5102
|
+
var dropdownStyles2 = {
|
|
5103
|
+
position: "fixed",
|
|
5104
|
+
backgroundColor: "var(--prismiq-color-background)",
|
|
5105
|
+
border: "1px solid var(--prismiq-color-border)",
|
|
5106
|
+
borderRadius: "var(--prismiq-radius-md)",
|
|
5107
|
+
boxShadow: "var(--prismiq-shadow-md)",
|
|
5108
|
+
zIndex: 1e3,
|
|
5109
|
+
maxHeight: "200px",
|
|
5110
|
+
overflow: "auto"
|
|
5111
|
+
};
|
|
5112
|
+
var optionStyles2 = {
|
|
5113
|
+
padding: "var(--prismiq-spacing-sm) var(--prismiq-spacing-md)",
|
|
5114
|
+
cursor: "pointer",
|
|
5115
|
+
fontSize: "var(--prismiq-font-size-sm)",
|
|
5116
|
+
transition: "background-color 0.1s"
|
|
5117
|
+
};
|
|
5118
|
+
var optionHoverStyles2 = {
|
|
5119
|
+
backgroundColor: "var(--prismiq-color-surface-hover)"
|
|
5120
|
+
};
|
|
4838
5121
|
function parseValue(value, dataType) {
|
|
4839
5122
|
if (!value) return void 0;
|
|
4840
5123
|
const type = dataType?.toLowerCase() ?? "";
|
|
@@ -4865,15 +5148,270 @@ function getInputType(dataType) {
|
|
|
4865
5148
|
}
|
|
4866
5149
|
return "text";
|
|
4867
5150
|
}
|
|
5151
|
+
function isMultiValueOperator(op) {
|
|
5152
|
+
return op === "in_" || op === "not_in" || op === "in_or_null";
|
|
5153
|
+
}
|
|
5154
|
+
var tagContainerStyles = {
|
|
5155
|
+
display: "flex",
|
|
5156
|
+
flexWrap: "wrap",
|
|
5157
|
+
alignItems: "center",
|
|
5158
|
+
gap: "4px",
|
|
5159
|
+
padding: "4px 8px",
|
|
5160
|
+
border: "1px solid var(--prismiq-color-border)",
|
|
5161
|
+
borderRadius: "var(--prismiq-radius-sm)",
|
|
5162
|
+
backgroundColor: "var(--prismiq-color-background)",
|
|
5163
|
+
minHeight: "32px",
|
|
5164
|
+
cursor: "text",
|
|
5165
|
+
flex: 1
|
|
5166
|
+
};
|
|
5167
|
+
var tagStyles = {
|
|
5168
|
+
display: "inline-flex",
|
|
5169
|
+
alignItems: "center",
|
|
5170
|
+
gap: "4px",
|
|
5171
|
+
padding: "1px 6px",
|
|
5172
|
+
backgroundColor: "var(--prismiq-color-surface)",
|
|
5173
|
+
border: "1px solid var(--prismiq-color-border)",
|
|
5174
|
+
borderRadius: "var(--prismiq-radius-sm)",
|
|
5175
|
+
fontSize: "var(--prismiq-font-size-sm)",
|
|
5176
|
+
lineHeight: "20px",
|
|
5177
|
+
whiteSpace: "nowrap"
|
|
5178
|
+
};
|
|
5179
|
+
var tagRemoveStyles = {
|
|
5180
|
+
display: "inline-flex",
|
|
5181
|
+
alignItems: "center",
|
|
5182
|
+
justifyContent: "center",
|
|
5183
|
+
width: "14px",
|
|
5184
|
+
height: "14px",
|
|
5185
|
+
border: "none",
|
|
5186
|
+
background: "none",
|
|
5187
|
+
cursor: "pointer",
|
|
5188
|
+
padding: 0,
|
|
5189
|
+
fontSize: "12px",
|
|
5190
|
+
lineHeight: 1,
|
|
5191
|
+
color: "var(--prismiq-color-text-muted)",
|
|
5192
|
+
borderRadius: "50%"
|
|
5193
|
+
};
|
|
5194
|
+
var tagInputStyles = {
|
|
5195
|
+
border: "none",
|
|
5196
|
+
outline: "none",
|
|
5197
|
+
background: "none",
|
|
5198
|
+
flex: 1,
|
|
5199
|
+
minWidth: "80px",
|
|
5200
|
+
fontSize: "var(--prismiq-font-size-sm)",
|
|
5201
|
+
padding: "2px 0",
|
|
5202
|
+
color: "var(--prismiq-color-text)"
|
|
5203
|
+
};
|
|
5204
|
+
var multiOptionCheckStyles = {
|
|
5205
|
+
marginRight: "6px",
|
|
5206
|
+
color: "var(--prismiq-color-primary)",
|
|
5207
|
+
fontWeight: 700,
|
|
5208
|
+
fontSize: "12px"
|
|
5209
|
+
};
|
|
4868
5210
|
function FilterValueInput({
|
|
4869
5211
|
operator,
|
|
4870
5212
|
value,
|
|
4871
5213
|
onChange,
|
|
4872
5214
|
dataType,
|
|
4873
5215
|
disabled = false,
|
|
4874
|
-
className
|
|
5216
|
+
className,
|
|
5217
|
+
tableName,
|
|
5218
|
+
columnName
|
|
4875
5219
|
}) {
|
|
5220
|
+
const { client } = useAnalytics();
|
|
4876
5221
|
const inputType = getInputType(dataType);
|
|
5222
|
+
const isMulti = isMultiValueOperator(operator);
|
|
5223
|
+
const [sampleValues, setSampleValues] = react.useState([]);
|
|
5224
|
+
const [isLoadingValues, setIsLoadingValues] = react.useState(false);
|
|
5225
|
+
const fetchedRef = react.useRef(null);
|
|
5226
|
+
const fetchSeqRef = react.useRef(0);
|
|
5227
|
+
const [isDropdownOpen, setIsDropdownOpen] = react.useState(false);
|
|
5228
|
+
const [highlightedIndex, setHighlightedIndex] = react.useState(-1);
|
|
5229
|
+
const [dropdownPosition, setDropdownPosition] = react.useState({ top: 0, left: 0, width: 0 });
|
|
5230
|
+
const inputRef = react.useRef(null);
|
|
5231
|
+
const dropdownRef = react.useRef(null);
|
|
5232
|
+
const containerRef = react.useRef(null);
|
|
5233
|
+
const [multiInputText, setMultiInputText] = react.useState("");
|
|
5234
|
+
react.useEffect(() => {
|
|
5235
|
+
setMultiInputText("");
|
|
5236
|
+
}, [operator]);
|
|
5237
|
+
react.useEffect(() => {
|
|
5238
|
+
if (!tableName || !columnName || !client) {
|
|
5239
|
+
setSampleValues([]);
|
|
5240
|
+
setIsLoadingValues(false);
|
|
5241
|
+
fetchedRef.current = null;
|
|
5242
|
+
return;
|
|
5243
|
+
}
|
|
5244
|
+
const fetchKey = `${tableName}.${columnName}`;
|
|
5245
|
+
if (fetchedRef.current === fetchKey) return;
|
|
5246
|
+
const fetchSeq = ++fetchSeqRef.current;
|
|
5247
|
+
const fetchSamples = async () => {
|
|
5248
|
+
setIsLoadingValues(true);
|
|
5249
|
+
try {
|
|
5250
|
+
const values = await client.getColumnSample(tableName, columnName, 100);
|
|
5251
|
+
const stringValues = values.filter((v) => v !== null && v !== void 0).map((v) => String(v));
|
|
5252
|
+
if (fetchSeqRef.current !== fetchSeq) return;
|
|
5253
|
+
setSampleValues(stringValues);
|
|
5254
|
+
fetchedRef.current = fetchKey;
|
|
5255
|
+
} catch (err) {
|
|
5256
|
+
if (fetchSeqRef.current !== fetchSeq) return;
|
|
5257
|
+
console.error("Failed to fetch sample values:", err);
|
|
5258
|
+
setSampleValues([]);
|
|
5259
|
+
fetchedRef.current = null;
|
|
5260
|
+
} finally {
|
|
5261
|
+
if (fetchSeqRef.current === fetchSeq) {
|
|
5262
|
+
setIsLoadingValues(false);
|
|
5263
|
+
}
|
|
5264
|
+
}
|
|
5265
|
+
};
|
|
5266
|
+
fetchSamples();
|
|
5267
|
+
}, [client, tableName, columnName]);
|
|
5268
|
+
const selectedValues = isMulti && Array.isArray(value) ? value.filter((v) => v !== null && v !== void 0).map((v) => String(v)) : [];
|
|
5269
|
+
const currentValueStr = isMulti ? multiInputText : formatValue(value);
|
|
5270
|
+
const filteredOptions = isMulti ? sampleValues.filter(
|
|
5271
|
+
(v) => v.toLowerCase().includes(multiInputText.toLowerCase()) && !selectedValues.includes(v)
|
|
5272
|
+
) : sampleValues.filter(
|
|
5273
|
+
(v) => v.toLowerCase().includes(currentValueStr.toLowerCase())
|
|
5274
|
+
);
|
|
5275
|
+
const updateDropdownPosition = react.useCallback(() => {
|
|
5276
|
+
const el = isMulti ? containerRef.current : inputRef.current;
|
|
5277
|
+
if (el) {
|
|
5278
|
+
const rect = el.getBoundingClientRect();
|
|
5279
|
+
setDropdownPosition({
|
|
5280
|
+
top: rect.bottom + 4,
|
|
5281
|
+
left: rect.left,
|
|
5282
|
+
width: rect.width
|
|
5283
|
+
});
|
|
5284
|
+
}
|
|
5285
|
+
}, [isMulti]);
|
|
5286
|
+
react.useEffect(() => {
|
|
5287
|
+
const handleClickOutside = (event) => {
|
|
5288
|
+
const target = event.target;
|
|
5289
|
+
const isInsideContainer = containerRef.current?.contains(target);
|
|
5290
|
+
const isInsideDropdown = dropdownRef.current?.contains(target);
|
|
5291
|
+
if (!isInsideContainer && !isInsideDropdown) {
|
|
5292
|
+
setIsDropdownOpen(false);
|
|
5293
|
+
}
|
|
5294
|
+
};
|
|
5295
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
5296
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
5297
|
+
}, []);
|
|
5298
|
+
const handleInputFocus = react.useCallback(() => {
|
|
5299
|
+
if (sampleValues.length > 0) {
|
|
5300
|
+
updateDropdownPosition();
|
|
5301
|
+
setIsDropdownOpen(true);
|
|
5302
|
+
setHighlightedIndex(-1);
|
|
5303
|
+
}
|
|
5304
|
+
}, [sampleValues.length, updateDropdownPosition]);
|
|
5305
|
+
const handleOptionSelect = react.useCallback(
|
|
5306
|
+
(optionValue) => {
|
|
5307
|
+
onChange(parseValue(optionValue, dataType));
|
|
5308
|
+
setIsDropdownOpen(false);
|
|
5309
|
+
inputRef.current?.blur();
|
|
5310
|
+
},
|
|
5311
|
+
[onChange, dataType]
|
|
5312
|
+
);
|
|
5313
|
+
const addMultiValue = react.useCallback(
|
|
5314
|
+
(val) => {
|
|
5315
|
+
const trimmed = val.trim();
|
|
5316
|
+
if (!trimmed) return;
|
|
5317
|
+
if (selectedValues.includes(trimmed)) return;
|
|
5318
|
+
const newValues = [...selectedValues, trimmed].map((v) => parseValue(v, dataType));
|
|
5319
|
+
onChange(newValues);
|
|
5320
|
+
setMultiInputText("");
|
|
5321
|
+
},
|
|
5322
|
+
[selectedValues, onChange, dataType]
|
|
5323
|
+
);
|
|
5324
|
+
const removeMultiValue = react.useCallback(
|
|
5325
|
+
(val) => {
|
|
5326
|
+
const newValues = selectedValues.filter((v) => v !== val).map((v) => parseValue(v, dataType));
|
|
5327
|
+
onChange(newValues.length > 0 ? newValues : []);
|
|
5328
|
+
},
|
|
5329
|
+
[selectedValues, onChange, dataType]
|
|
5330
|
+
);
|
|
5331
|
+
const handleMultiOptionSelect = react.useCallback(
|
|
5332
|
+
(optionValue) => {
|
|
5333
|
+
if (selectedValues.includes(optionValue)) {
|
|
5334
|
+
removeMultiValue(optionValue);
|
|
5335
|
+
} else {
|
|
5336
|
+
addMultiValue(optionValue);
|
|
5337
|
+
}
|
|
5338
|
+
setMultiInputText("");
|
|
5339
|
+
updateDropdownPosition();
|
|
5340
|
+
inputRef.current?.focus();
|
|
5341
|
+
},
|
|
5342
|
+
[selectedValues, addMultiValue, removeMultiValue, updateDropdownPosition]
|
|
5343
|
+
);
|
|
5344
|
+
const handleMultiInputKeyDown = react.useCallback(
|
|
5345
|
+
(e) => {
|
|
5346
|
+
if (e.key === "Backspace" && multiInputText === "" && selectedValues.length > 0) {
|
|
5347
|
+
const lastVal = selectedValues[selectedValues.length - 1];
|
|
5348
|
+
if (lastVal !== void 0) removeMultiValue(lastVal);
|
|
5349
|
+
return;
|
|
5350
|
+
}
|
|
5351
|
+
if (isDropdownOpen && filteredOptions.length > 0) {
|
|
5352
|
+
switch (e.key) {
|
|
5353
|
+
case "ArrowDown":
|
|
5354
|
+
e.preventDefault();
|
|
5355
|
+
setHighlightedIndex((prev) => Math.min(prev + 1, filteredOptions.length - 1));
|
|
5356
|
+
return;
|
|
5357
|
+
case "ArrowUp":
|
|
5358
|
+
e.preventDefault();
|
|
5359
|
+
setHighlightedIndex((prev) => Math.max(prev - 1, 0));
|
|
5360
|
+
return;
|
|
5361
|
+
case "Enter":
|
|
5362
|
+
e.preventDefault();
|
|
5363
|
+
if (highlightedIndex >= 0 && filteredOptions[highlightedIndex]) {
|
|
5364
|
+
handleMultiOptionSelect(filteredOptions[highlightedIndex]);
|
|
5365
|
+
} else if (multiInputText.trim()) {
|
|
5366
|
+
addMultiValue(multiInputText);
|
|
5367
|
+
}
|
|
5368
|
+
return;
|
|
5369
|
+
case "Escape":
|
|
5370
|
+
setIsDropdownOpen(false);
|
|
5371
|
+
return;
|
|
5372
|
+
}
|
|
5373
|
+
}
|
|
5374
|
+
if (e.key === "," || e.key === "Enter") {
|
|
5375
|
+
e.preventDefault();
|
|
5376
|
+
addMultiValue(multiInputText);
|
|
5377
|
+
}
|
|
5378
|
+
},
|
|
5379
|
+
[
|
|
5380
|
+
multiInputText,
|
|
5381
|
+
selectedValues,
|
|
5382
|
+
isDropdownOpen,
|
|
5383
|
+
filteredOptions,
|
|
5384
|
+
highlightedIndex,
|
|
5385
|
+
addMultiValue,
|
|
5386
|
+
removeMultiValue,
|
|
5387
|
+
handleMultiOptionSelect
|
|
5388
|
+
]
|
|
5389
|
+
);
|
|
5390
|
+
const handleSingleKeyDown = react.useCallback(
|
|
5391
|
+
(e) => {
|
|
5392
|
+
if (!isDropdownOpen || filteredOptions.length === 0) return;
|
|
5393
|
+
switch (e.key) {
|
|
5394
|
+
case "ArrowDown":
|
|
5395
|
+
e.preventDefault();
|
|
5396
|
+
setHighlightedIndex((prev) => Math.min(prev + 1, filteredOptions.length - 1));
|
|
5397
|
+
break;
|
|
5398
|
+
case "ArrowUp":
|
|
5399
|
+
e.preventDefault();
|
|
5400
|
+
setHighlightedIndex((prev) => Math.max(prev - 1, 0));
|
|
5401
|
+
break;
|
|
5402
|
+
case "Enter":
|
|
5403
|
+
e.preventDefault();
|
|
5404
|
+
if (highlightedIndex >= 0 && filteredOptions[highlightedIndex]) {
|
|
5405
|
+
handleOptionSelect(filteredOptions[highlightedIndex]);
|
|
5406
|
+
}
|
|
5407
|
+
break;
|
|
5408
|
+
case "Escape":
|
|
5409
|
+
setIsDropdownOpen(false);
|
|
5410
|
+
break;
|
|
5411
|
+
}
|
|
5412
|
+
},
|
|
5413
|
+
[isDropdownOpen, filteredOptions, highlightedIndex, handleOptionSelect]
|
|
5414
|
+
);
|
|
4877
5415
|
if (operator === "is_null" || operator === "is_not_null") {
|
|
4878
5416
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
|
|
4879
5417
|
}
|
|
@@ -4913,36 +5451,163 @@ function FilterValueInput({
|
|
|
4913
5451
|
)
|
|
4914
5452
|
] });
|
|
4915
5453
|
}
|
|
4916
|
-
if (
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
5454
|
+
if (isMulti) {
|
|
5455
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: containerStyles7, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, style: comboboxContainerStyles, children: [
|
|
5456
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5457
|
+
"div",
|
|
5458
|
+
{
|
|
5459
|
+
"data-testid": "filter-tag-container",
|
|
5460
|
+
style: tagContainerStyles,
|
|
5461
|
+
onClick: () => inputRef.current?.focus(),
|
|
5462
|
+
children: [
|
|
5463
|
+
selectedValues.map((val) => /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-testid": `filter-tag-${val}`, style: tagStyles, children: [
|
|
5464
|
+
val,
|
|
5465
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5466
|
+
"button",
|
|
5467
|
+
{
|
|
5468
|
+
type: "button",
|
|
5469
|
+
"data-testid": `filter-tag-remove-${val}`,
|
|
5470
|
+
style: tagRemoveStyles,
|
|
5471
|
+
onClick: (e) => {
|
|
5472
|
+
e.stopPropagation();
|
|
5473
|
+
removeMultiValue(val);
|
|
5474
|
+
},
|
|
5475
|
+
tabIndex: -1,
|
|
5476
|
+
children: "\xD7"
|
|
5477
|
+
}
|
|
5478
|
+
)
|
|
5479
|
+
] }, val)),
|
|
5480
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5481
|
+
"input",
|
|
5482
|
+
{
|
|
5483
|
+
ref: inputRef,
|
|
5484
|
+
"data-testid": "filter-multi-input",
|
|
5485
|
+
type: "text",
|
|
5486
|
+
placeholder: selectedValues.length === 0 ? isLoadingValues ? "Loading..." : "Type or select values" : "",
|
|
5487
|
+
value: multiInputText,
|
|
5488
|
+
disabled: disabled || isLoadingValues,
|
|
5489
|
+
onChange: (e) => {
|
|
5490
|
+
setMultiInputText(e.target.value);
|
|
5491
|
+
if (sampleValues.length > 0) {
|
|
5492
|
+
updateDropdownPosition();
|
|
5493
|
+
setIsDropdownOpen(true);
|
|
5494
|
+
setHighlightedIndex(-1);
|
|
5495
|
+
}
|
|
5496
|
+
},
|
|
5497
|
+
onFocus: handleInputFocus,
|
|
5498
|
+
onKeyDown: handleMultiInputKeyDown,
|
|
5499
|
+
style: tagInputStyles
|
|
5500
|
+
}
|
|
5501
|
+
)
|
|
5502
|
+
]
|
|
5503
|
+
}
|
|
5504
|
+
),
|
|
5505
|
+
selectedValues.length === 0 && !multiInputText && /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": "filter-multi-hint", style: {
|
|
5506
|
+
fontSize: "11px",
|
|
5507
|
+
color: "var(--prismiq-color-text-muted)",
|
|
5508
|
+
marginTop: "2px",
|
|
5509
|
+
paddingLeft: "2px"
|
|
5510
|
+
}, children: [
|
|
5511
|
+
"Press ",
|
|
5512
|
+
/* @__PURE__ */ jsxRuntime.jsx("kbd", { style: { padding: "0 3px", border: "1px solid var(--prismiq-color-border)", borderRadius: "3px", fontSize: "10px" }, children: "," }),
|
|
5513
|
+
" or ",
|
|
5514
|
+
/* @__PURE__ */ jsxRuntime.jsx("kbd", { style: { padding: "0 3px", border: "1px solid var(--prismiq-color-border)", borderRadius: "3px", fontSize: "10px" }, children: "Enter" }),
|
|
5515
|
+
" to add values"
|
|
5516
|
+
] }),
|
|
5517
|
+
isDropdownOpen && filteredOptions.length > 0 && typeof document !== "undefined" && reactDom.createPortal(
|
|
5518
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5519
|
+
"div",
|
|
5520
|
+
{
|
|
5521
|
+
ref: dropdownRef,
|
|
5522
|
+
"data-testid": "filter-dropdown",
|
|
5523
|
+
style: {
|
|
5524
|
+
...dropdownStyles2,
|
|
5525
|
+
top: dropdownPosition.top,
|
|
5526
|
+
left: dropdownPosition.left,
|
|
5527
|
+
width: dropdownPosition.width
|
|
5528
|
+
},
|
|
5529
|
+
children: filteredOptions.map((optionValue, index) => {
|
|
5530
|
+
const isSelected = selectedValues.includes(optionValue);
|
|
5531
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5532
|
+
"div",
|
|
5533
|
+
{
|
|
5534
|
+
"data-testid": `filter-option-${index}`,
|
|
5535
|
+
onClick: () => handleMultiOptionSelect(optionValue),
|
|
5536
|
+
onMouseEnter: () => setHighlightedIndex(index),
|
|
5537
|
+
style: {
|
|
5538
|
+
...optionStyles2,
|
|
5539
|
+
...index === highlightedIndex ? optionHoverStyles2 : {},
|
|
5540
|
+
...isSelected ? { backgroundColor: "var(--prismiq-color-surface)", fontWeight: 500 } : {}
|
|
5541
|
+
},
|
|
5542
|
+
children: [
|
|
5543
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: multiOptionCheckStyles, children: isSelected ? "\u2713" : "\u2003" }),
|
|
5544
|
+
optionValue
|
|
5545
|
+
]
|
|
5546
|
+
},
|
|
5547
|
+
`${optionValue}-${index}`
|
|
5548
|
+
);
|
|
5549
|
+
})
|
|
5550
|
+
}
|
|
5551
|
+
),
|
|
5552
|
+
document.body
|
|
5553
|
+
)
|
|
5554
|
+
] }) });
|
|
5555
|
+
}
|
|
5556
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: containerStyles7, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, style: comboboxContainerStyles, children: [
|
|
5557
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4922
5558
|
Input,
|
|
4923
5559
|
{
|
|
5560
|
+
ref: inputRef,
|
|
5561
|
+
"data-testid": "filter-single-input",
|
|
4924
5562
|
inputSize: "sm",
|
|
4925
|
-
type:
|
|
4926
|
-
placeholder:
|
|
4927
|
-
value:
|
|
4928
|
-
disabled,
|
|
4929
|
-
onChange:
|
|
4930
|
-
|
|
5563
|
+
type: inputType,
|
|
5564
|
+
placeholder: isLoadingValues ? "Loading..." : "Type or select value",
|
|
5565
|
+
value: currentValueStr,
|
|
5566
|
+
disabled: disabled || isLoadingValues,
|
|
5567
|
+
onChange: (e) => {
|
|
5568
|
+
onChange(parseValue(e.target.value, dataType));
|
|
5569
|
+
if (sampleValues.length > 0) {
|
|
5570
|
+
updateDropdownPosition();
|
|
5571
|
+
setIsDropdownOpen(true);
|
|
5572
|
+
}
|
|
5573
|
+
},
|
|
5574
|
+
onFocus: handleInputFocus,
|
|
5575
|
+
onKeyDown: handleSingleKeyDown,
|
|
5576
|
+
style: { width: "100%" }
|
|
4931
5577
|
}
|
|
4932
|
-
)
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
5578
|
+
),
|
|
5579
|
+
isDropdownOpen && filteredOptions.length > 0 && typeof document !== "undefined" && reactDom.createPortal(
|
|
5580
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5581
|
+
"div",
|
|
5582
|
+
{
|
|
5583
|
+
ref: dropdownRef,
|
|
5584
|
+
"data-testid": "filter-dropdown",
|
|
5585
|
+
style: {
|
|
5586
|
+
...dropdownStyles2,
|
|
5587
|
+
top: dropdownPosition.top,
|
|
5588
|
+
left: dropdownPosition.left,
|
|
5589
|
+
width: dropdownPosition.width
|
|
5590
|
+
},
|
|
5591
|
+
children: filteredOptions.map((optionValue, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
5592
|
+
"div",
|
|
5593
|
+
{
|
|
5594
|
+
"data-testid": `filter-option-${index}`,
|
|
5595
|
+
onClick: () => handleOptionSelect(optionValue),
|
|
5596
|
+
onMouseEnter: () => setHighlightedIndex(index),
|
|
5597
|
+
style: {
|
|
5598
|
+
...optionStyles2,
|
|
5599
|
+
...index === highlightedIndex ? optionHoverStyles2 : {},
|
|
5600
|
+
...optionValue === currentValueStr ? { backgroundColor: "var(--prismiq-color-surface)", fontWeight: 500 } : {}
|
|
5601
|
+
},
|
|
5602
|
+
children: optionValue
|
|
5603
|
+
},
|
|
5604
|
+
`${optionValue}-${index}`
|
|
5605
|
+
))
|
|
5606
|
+
}
|
|
5607
|
+
),
|
|
5608
|
+
document.body
|
|
5609
|
+
)
|
|
5610
|
+
] }) });
|
|
4946
5611
|
}
|
|
4947
5612
|
var rowStyles = {
|
|
4948
5613
|
display: "flex",
|
|
@@ -5035,13 +5700,16 @@ function FilterRow({
|
|
|
5035
5700
|
});
|
|
5036
5701
|
return options;
|
|
5037
5702
|
}, [tables, schema]);
|
|
5703
|
+
const currentTable = react.useMemo(
|
|
5704
|
+
() => tables.find((t) => t.id === filter.table_id),
|
|
5705
|
+
[tables, filter.table_id]
|
|
5706
|
+
);
|
|
5038
5707
|
const currentColumnSchema = react.useMemo(() => {
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
const tableSchema = schema.tables.find((t) => t.name === table.name);
|
|
5708
|
+
if (!currentTable) return void 0;
|
|
5709
|
+
const tableSchema = schema.tables.find((t) => t.name === currentTable.name);
|
|
5042
5710
|
if (!tableSchema) return void 0;
|
|
5043
5711
|
return tableSchema.columns.find((c) => c.name === filter.column);
|
|
5044
|
-
}, [
|
|
5712
|
+
}, [currentTable, schema, filter.column]);
|
|
5045
5713
|
const operatorOptions = react.useMemo(
|
|
5046
5714
|
() => getOperatorsForType(currentColumnSchema?.data_type),
|
|
5047
5715
|
[currentColumnSchema]
|
|
@@ -5102,7 +5770,9 @@ function FilterRow({
|
|
|
5102
5770
|
operator: filter.operator,
|
|
5103
5771
|
value: filter.value,
|
|
5104
5772
|
onChange: handleValueChange,
|
|
5105
|
-
dataType: currentColumnSchema?.data_type
|
|
5773
|
+
dataType: currentColumnSchema?.data_type,
|
|
5774
|
+
tableName: currentTable?.name,
|
|
5775
|
+
columnName: filter.column
|
|
5106
5776
|
}
|
|
5107
5777
|
) }),
|
|
5108
5778
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -7480,7 +8150,7 @@ function TimeSeriesConfig({
|
|
|
7480
8150
|
setError("No date column available. Add a date column to the query first.");
|
|
7481
8151
|
return;
|
|
7482
8152
|
}
|
|
7483
|
-
const parsed =
|
|
8153
|
+
const parsed = chunkKXB2IZI2_cjs.parseColumnRef(currentDateColumn, "t1");
|
|
7484
8154
|
if (!parsed) {
|
|
7485
8155
|
setError("Invalid date column reference. Please select a valid date column.");
|
|
7486
8156
|
return;
|
|
@@ -7494,7 +8164,7 @@ function TimeSeriesConfig({
|
|
|
7494
8164
|
};
|
|
7495
8165
|
const handleDateColumnChange = (value) => {
|
|
7496
8166
|
if (!config || !value) return;
|
|
7497
|
-
const parsed =
|
|
8167
|
+
const parsed = chunkKXB2IZI2_cjs.parseColumnRef(value, config.table_id);
|
|
7498
8168
|
if (!parsed) {
|
|
7499
8169
|
setError("Invalid column reference. Please select a valid date column.");
|
|
7500
8170
|
return;
|
|
@@ -8418,7 +9088,14 @@ function CustomSQLEditor({
|
|
|
8418
9088
|
style
|
|
8419
9089
|
}) {
|
|
8420
9090
|
const [sql, setSql] = react.useState(initialSql);
|
|
9091
|
+
const prevInitialSqlRef = react.useRef(initialSql);
|
|
8421
9092
|
const [isFocused, setIsFocused] = react.useState(false);
|
|
9093
|
+
react.useEffect(() => {
|
|
9094
|
+
if (initialSql !== prevInitialSqlRef.current) {
|
|
9095
|
+
prevInitialSqlRef.current = initialSql;
|
|
9096
|
+
setSql(initialSql);
|
|
9097
|
+
}
|
|
9098
|
+
}, [initialSql]);
|
|
8422
9099
|
const [executeEnabled, setExecuteEnabled] = react.useState(false);
|
|
8423
9100
|
const [lastExecutedSql, setLastExecutedSql] = react.useState(null);
|
|
8424
9101
|
const {
|
|
@@ -8464,7 +9141,7 @@ function CustomSQLEditor({
|
|
|
8464
9141
|
...buttonStyles,
|
|
8465
9142
|
...canExecute ? {} : buttonDisabledStyles
|
|
8466
9143
|
};
|
|
8467
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: { ...containerStyles17, ...style }, children: [
|
|
9144
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: { ...containerStyles17, ...style }, "data-testid": "custom-sql-editor", children: [
|
|
8468
9145
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: editorWrapperStyles, children: [
|
|
8469
9146
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8470
9147
|
"textarea",
|
|
@@ -8478,7 +9155,8 @@ function CustomSQLEditor({
|
|
|
8478
9155
|
style: mergedTextareaStyles,
|
|
8479
9156
|
spellCheck: false,
|
|
8480
9157
|
autoComplete: "off",
|
|
8481
|
-
autoCapitalize: "off"
|
|
9158
|
+
autoCapitalize: "off",
|
|
9159
|
+
"data-testid": "custom-sql-textarea"
|
|
8482
9160
|
}
|
|
8483
9161
|
),
|
|
8484
9162
|
validation && !validation.valid && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: validationErrorStyles, children: [
|
|
@@ -8502,6 +9180,7 @@ function CustomSQLEditor({
|
|
|
8502
9180
|
disabled: !canExecute,
|
|
8503
9181
|
style: mergedButtonStyles,
|
|
8504
9182
|
type: "button",
|
|
9183
|
+
"data-testid": "custom-sql-run-button",
|
|
8505
9184
|
children: [
|
|
8506
9185
|
isLoading ? "Executing..." : "Run Query",
|
|
8507
9186
|
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "11px", opacity: 0.7 }, children: "(Cmd+Enter)" })
|
|
@@ -8543,13 +9222,14 @@ function TableSelector({
|
|
|
8543
9222
|
className
|
|
8544
9223
|
}) {
|
|
8545
9224
|
const { theme } = chunkLMTG3LRC_cjs.useTheme();
|
|
9225
|
+
const { getDisplayName } = useSchema();
|
|
8546
9226
|
const availableTableOptions = react.useMemo(() => {
|
|
8547
9227
|
const selectedNames = new Set(tables.map((t) => t.name));
|
|
8548
9228
|
return schema.tables.filter((t) => !selectedNames.has(t.name)).map((t) => ({
|
|
8549
9229
|
value: t.name,
|
|
8550
|
-
label:
|
|
9230
|
+
label: getDisplayName(t.name)
|
|
8551
9231
|
}));
|
|
8552
|
-
}, [schema.tables, tables]);
|
|
9232
|
+
}, [schema.tables, tables, getDisplayName]);
|
|
8553
9233
|
const suggestedTables = react.useMemo(() => {
|
|
8554
9234
|
if (!showRelationships || tables.length === 0) return [];
|
|
8555
9235
|
const selectedNames = new Set(tables.map((t) => t.name));
|
|
@@ -8651,7 +9331,7 @@ function TableSelector({
|
|
|
8651
9331
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: containerStyle, children: [
|
|
8652
9332
|
tables.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: selectedTablesStyle, children: tables.map((table, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: tableChipStyle, children: [
|
|
8653
9333
|
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "table", size: 14 }),
|
|
8654
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: table.name }),
|
|
9334
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: getDisplayName(table.name) }),
|
|
8655
9335
|
index === 0 && /* @__PURE__ */ jsxRuntime.jsx(Badge, { size: "sm", variant: "default", children: "primary" }),
|
|
8656
9336
|
tables.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8657
9337
|
"button",
|
|
@@ -8659,7 +9339,7 @@ function TableSelector({
|
|
|
8659
9339
|
type: "button",
|
|
8660
9340
|
style: removeButtonStyle,
|
|
8661
9341
|
onClick: () => handleRemoveTable(table.id),
|
|
8662
|
-
"aria-label": `Remove ${table.name}`,
|
|
9342
|
+
"aria-label": `Remove ${getDisplayName(table.name)}`,
|
|
8663
9343
|
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "x", size: 12 })
|
|
8664
9344
|
}
|
|
8665
9345
|
)
|
|
@@ -8693,7 +9373,7 @@ function TableSelector({
|
|
|
8693
9373
|
title: suggestion.relationship,
|
|
8694
9374
|
children: [
|
|
8695
9375
|
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "plus", size: 10 }),
|
|
8696
|
-
suggestion.table
|
|
9376
|
+
getDisplayName(suggestion.table)
|
|
8697
9377
|
]
|
|
8698
9378
|
},
|
|
8699
9379
|
suggestion.table
|
|
@@ -8702,6 +9382,257 @@ function TableSelector({
|
|
|
8702
9382
|
] })
|
|
8703
9383
|
] });
|
|
8704
9384
|
}
|
|
9385
|
+
function parseContent(content) {
|
|
9386
|
+
const startToken = "```sql";
|
|
9387
|
+
const endToken = "```";
|
|
9388
|
+
const parts = [];
|
|
9389
|
+
let cursor = 0;
|
|
9390
|
+
while (cursor < content.length) {
|
|
9391
|
+
const start = content.indexOf(startToken, cursor);
|
|
9392
|
+
if (start === -1) break;
|
|
9393
|
+
if (start > cursor) {
|
|
9394
|
+
parts.push({ type: "text", value: content.slice(cursor, start) });
|
|
9395
|
+
}
|
|
9396
|
+
const sqlStart = content.indexOf("\n", start + startToken.length);
|
|
9397
|
+
if (sqlStart === -1) break;
|
|
9398
|
+
const end = content.indexOf(endToken, sqlStart + 1);
|
|
9399
|
+
if (end === -1) break;
|
|
9400
|
+
const sql = content.slice(sqlStart + 1, end).trim();
|
|
9401
|
+
if (sql) {
|
|
9402
|
+
parts.push({ type: "sql", value: sql });
|
|
9403
|
+
}
|
|
9404
|
+
cursor = end + endToken.length;
|
|
9405
|
+
}
|
|
9406
|
+
if (cursor < content.length) {
|
|
9407
|
+
parts.push({ type: "text", value: content.slice(cursor) });
|
|
9408
|
+
}
|
|
9409
|
+
return parts;
|
|
9410
|
+
}
|
|
9411
|
+
function ChatBubble({ message, onApplySql }) {
|
|
9412
|
+
const { theme } = chunkLMTG3LRC_cjs.useTheme();
|
|
9413
|
+
const isUser = message.role === "user";
|
|
9414
|
+
const parts = react.useMemo(() => parseContent(message.content), [message.content]);
|
|
9415
|
+
const bubbleStyle = {
|
|
9416
|
+
maxWidth: "85%",
|
|
9417
|
+
padding: `${theme.spacing.sm} ${theme.spacing.md}`,
|
|
9418
|
+
borderRadius: theme.radius.md,
|
|
9419
|
+
fontSize: theme.fontSizes.sm,
|
|
9420
|
+
lineHeight: 1.5,
|
|
9421
|
+
whiteSpace: "pre-wrap",
|
|
9422
|
+
wordBreak: "break-word",
|
|
9423
|
+
alignSelf: isUser ? "flex-end" : "flex-start",
|
|
9424
|
+
backgroundColor: isUser ? theme.colors.primary : theme.colors.surface,
|
|
9425
|
+
color: isUser ? "#fff" : theme.colors.text,
|
|
9426
|
+
border: isUser ? "none" : `1px solid ${theme.colors.border}`
|
|
9427
|
+
};
|
|
9428
|
+
const sqlBlockStyle = {
|
|
9429
|
+
backgroundColor: isUser ? "rgba(0,0,0,0.2)" : theme.colors.background,
|
|
9430
|
+
borderRadius: theme.radius.sm,
|
|
9431
|
+
padding: theme.spacing.sm,
|
|
9432
|
+
margin: `${theme.spacing.xs} 0`,
|
|
9433
|
+
fontFamily: theme.fonts.mono,
|
|
9434
|
+
fontSize: theme.fontSizes.xs,
|
|
9435
|
+
overflow: "auto",
|
|
9436
|
+
position: "relative"
|
|
9437
|
+
};
|
|
9438
|
+
const applyBtnContainerStyle = {
|
|
9439
|
+
display: "flex",
|
|
9440
|
+
justifyContent: "flex-end",
|
|
9441
|
+
marginTop: theme.spacing.xs
|
|
9442
|
+
};
|
|
9443
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: bubbleStyle, children: parts.map((part, i) => {
|
|
9444
|
+
if (part.type === "sql") {
|
|
9445
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": `chat-sql-${i}`, children: [
|
|
9446
|
+
/* @__PURE__ */ jsxRuntime.jsx("pre", { style: sqlBlockStyle, children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: part.value }) }),
|
|
9447
|
+
onApplySql && /* @__PURE__ */ jsxRuntime.jsx("div", { style: applyBtnContainerStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
9448
|
+
Button,
|
|
9449
|
+
{
|
|
9450
|
+
variant: "ghost",
|
|
9451
|
+
size: "sm",
|
|
9452
|
+
onClick: () => onApplySql(part.value),
|
|
9453
|
+
"data-testid": `apply-sql-btn-${i}`,
|
|
9454
|
+
children: "Apply to Editor"
|
|
9455
|
+
}
|
|
9456
|
+
) })
|
|
9457
|
+
] }, i);
|
|
9458
|
+
}
|
|
9459
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { children: part.value }, i);
|
|
9460
|
+
}) });
|
|
9461
|
+
}
|
|
9462
|
+
function ChatPanel({ currentSql, onApplySql }) {
|
|
9463
|
+
const { theme } = chunkLMTG3LRC_cjs.useTheme();
|
|
9464
|
+
const {
|
|
9465
|
+
messages,
|
|
9466
|
+
isStreaming,
|
|
9467
|
+
streamingContent,
|
|
9468
|
+
suggestedSql,
|
|
9469
|
+
sendMessage,
|
|
9470
|
+
clearHistory,
|
|
9471
|
+
error
|
|
9472
|
+
} = useLLMChat();
|
|
9473
|
+
const [input, setInput] = react.useState("");
|
|
9474
|
+
const messagesEndRef = react.useRef(null);
|
|
9475
|
+
react.useEffect(() => {
|
|
9476
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
9477
|
+
}, [messages, streamingContent]);
|
|
9478
|
+
const handleSend = react.useCallback(() => {
|
|
9479
|
+
const trimmed = input.trim();
|
|
9480
|
+
if (!trimmed) return;
|
|
9481
|
+
setInput("");
|
|
9482
|
+
void sendMessage(trimmed, currentSql);
|
|
9483
|
+
}, [input, currentSql, sendMessage]);
|
|
9484
|
+
const handleKeyDown = react.useCallback(
|
|
9485
|
+
(e) => {
|
|
9486
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
9487
|
+
e.preventDefault();
|
|
9488
|
+
handleSend();
|
|
9489
|
+
}
|
|
9490
|
+
},
|
|
9491
|
+
[handleSend]
|
|
9492
|
+
);
|
|
9493
|
+
const containerStyle = {
|
|
9494
|
+
display: "flex",
|
|
9495
|
+
flexDirection: "column",
|
|
9496
|
+
height: "100%",
|
|
9497
|
+
borderLeft: `1px solid ${theme.colors.border}`,
|
|
9498
|
+
backgroundColor: theme.colors.background
|
|
9499
|
+
};
|
|
9500
|
+
const headerStyle = {
|
|
9501
|
+
display: "flex",
|
|
9502
|
+
alignItems: "center",
|
|
9503
|
+
justifyContent: "space-between",
|
|
9504
|
+
padding: `${theme.spacing.sm} ${theme.spacing.md}`,
|
|
9505
|
+
borderBottom: `1px solid ${theme.colors.border}`,
|
|
9506
|
+
flexShrink: 0
|
|
9507
|
+
};
|
|
9508
|
+
const headerTitleStyle = {
|
|
9509
|
+
display: "flex",
|
|
9510
|
+
alignItems: "center",
|
|
9511
|
+
gap: theme.spacing.xs,
|
|
9512
|
+
fontSize: theme.fontSizes.sm,
|
|
9513
|
+
fontWeight: 600,
|
|
9514
|
+
color: theme.colors.text
|
|
9515
|
+
};
|
|
9516
|
+
const messagesStyle = {
|
|
9517
|
+
flex: 1,
|
|
9518
|
+
overflow: "auto",
|
|
9519
|
+
padding: theme.spacing.md,
|
|
9520
|
+
display: "flex",
|
|
9521
|
+
flexDirection: "column",
|
|
9522
|
+
gap: theme.spacing.md
|
|
9523
|
+
};
|
|
9524
|
+
const streamingStyle = {
|
|
9525
|
+
alignSelf: "flex-start",
|
|
9526
|
+
maxWidth: "85%",
|
|
9527
|
+
padding: `${theme.spacing.sm} ${theme.spacing.md}`,
|
|
9528
|
+
borderRadius: theme.radius.md,
|
|
9529
|
+
fontSize: theme.fontSizes.sm,
|
|
9530
|
+
lineHeight: 1.5,
|
|
9531
|
+
whiteSpace: "pre-wrap",
|
|
9532
|
+
wordBreak: "break-word",
|
|
9533
|
+
backgroundColor: theme.colors.surface,
|
|
9534
|
+
color: theme.colors.text,
|
|
9535
|
+
border: `1px solid ${theme.colors.border}`
|
|
9536
|
+
};
|
|
9537
|
+
const suggestedSqlStyle = {
|
|
9538
|
+
padding: theme.spacing.sm,
|
|
9539
|
+
borderTop: `1px solid ${theme.colors.border}`,
|
|
9540
|
+
display: "flex",
|
|
9541
|
+
alignItems: "center",
|
|
9542
|
+
justifyContent: "center",
|
|
9543
|
+
flexShrink: 0
|
|
9544
|
+
};
|
|
9545
|
+
const inputAreaStyle = {
|
|
9546
|
+
display: "flex",
|
|
9547
|
+
gap: theme.spacing.sm,
|
|
9548
|
+
padding: theme.spacing.sm,
|
|
9549
|
+
borderTop: `1px solid ${theme.colors.border}`,
|
|
9550
|
+
flexShrink: 0
|
|
9551
|
+
};
|
|
9552
|
+
const textareaStyle = {
|
|
9553
|
+
flex: 1,
|
|
9554
|
+
resize: "none",
|
|
9555
|
+
border: `1px solid ${theme.colors.border}`,
|
|
9556
|
+
borderRadius: theme.radius.sm,
|
|
9557
|
+
padding: theme.spacing.sm,
|
|
9558
|
+
fontSize: theme.fontSizes.sm,
|
|
9559
|
+
fontFamily: theme.fonts.sans,
|
|
9560
|
+
backgroundColor: theme.colors.surface,
|
|
9561
|
+
color: theme.colors.text,
|
|
9562
|
+
outline: "none",
|
|
9563
|
+
minHeight: "36px",
|
|
9564
|
+
maxHeight: "120px"
|
|
9565
|
+
};
|
|
9566
|
+
const emptyStyle = {
|
|
9567
|
+
flex: 1,
|
|
9568
|
+
display: "flex",
|
|
9569
|
+
alignItems: "center",
|
|
9570
|
+
justifyContent: "center",
|
|
9571
|
+
textAlign: "center",
|
|
9572
|
+
padding: theme.spacing.lg,
|
|
9573
|
+
color: theme.colors.textMuted,
|
|
9574
|
+
fontSize: theme.fontSizes.sm
|
|
9575
|
+
};
|
|
9576
|
+
const errorStyle = {
|
|
9577
|
+
padding: theme.spacing.sm,
|
|
9578
|
+
margin: `0 ${theme.spacing.md}`,
|
|
9579
|
+
borderRadius: theme.radius.sm,
|
|
9580
|
+
backgroundColor: "rgba(239, 68, 68, 0.1)",
|
|
9581
|
+
color: "#ef4444",
|
|
9582
|
+
fontSize: theme.fontSizes.xs,
|
|
9583
|
+
flexShrink: 0
|
|
9584
|
+
};
|
|
9585
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: containerStyle, className: "prismiq-chat-panel", "data-testid": "chat-panel-root", children: [
|
|
9586
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: headerStyle, children: [
|
|
9587
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: headerTitleStyle, children: [
|
|
9588
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "edit", size: 16 }),
|
|
9589
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "SQL Assistant" })
|
|
9590
|
+
] }),
|
|
9591
|
+
messages.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", onClick: clearHistory, "data-testid": "chat-clear", children: "Clear" })
|
|
9592
|
+
] }),
|
|
9593
|
+
messages.length === 0 && !isStreaming ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: emptyStyle, "data-testid": "chat-empty", children: [
|
|
9594
|
+
"Ask me to help write SQL queries.",
|
|
9595
|
+
"\n",
|
|
9596
|
+
"I can see your database schema and validate queries."
|
|
9597
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: messagesStyle, "data-testid": "chat-messages", children: [
|
|
9598
|
+
messages.map((msg, i) => /* @__PURE__ */ jsxRuntime.jsx(ChatBubble, { message: msg, onApplySql }, i)),
|
|
9599
|
+
isStreaming && streamingContent && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: streamingStyle, "data-testid": "chat-streaming", children: [
|
|
9600
|
+
streamingContent,
|
|
9601
|
+
"\u258D"
|
|
9602
|
+
] }),
|
|
9603
|
+
isStreaming && !streamingContent && /* @__PURE__ */ jsxRuntime.jsx("div", { style: streamingStyle, "data-testid": "chat-streaming", children: "Thinking..." }),
|
|
9604
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: messagesEndRef })
|
|
9605
|
+
] }),
|
|
9606
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: errorStyle, "data-testid": "chat-error", children: error }),
|
|
9607
|
+
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" }) }),
|
|
9608
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: inputAreaStyle, children: [
|
|
9609
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9610
|
+
"textarea",
|
|
9611
|
+
{
|
|
9612
|
+
style: textareaStyle,
|
|
9613
|
+
value: input,
|
|
9614
|
+
onChange: (e) => setInput(e.target.value),
|
|
9615
|
+
onKeyDown: handleKeyDown,
|
|
9616
|
+
placeholder: "Ask about your data...",
|
|
9617
|
+
rows: 1,
|
|
9618
|
+
disabled: isStreaming,
|
|
9619
|
+
"data-testid": "chat-input"
|
|
9620
|
+
}
|
|
9621
|
+
),
|
|
9622
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9623
|
+
Button,
|
|
9624
|
+
{
|
|
9625
|
+
variant: "primary",
|
|
9626
|
+
size: "sm",
|
|
9627
|
+
onClick: handleSend,
|
|
9628
|
+
disabled: isStreaming || !input.trim(),
|
|
9629
|
+
"data-testid": "chat-send",
|
|
9630
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "play", size: 16 })
|
|
9631
|
+
}
|
|
9632
|
+
)
|
|
9633
|
+
] })
|
|
9634
|
+
] });
|
|
9635
|
+
}
|
|
8705
9636
|
|
|
8706
9637
|
exports.AggregationPicker = AggregationPicker;
|
|
8707
9638
|
exports.AnalyticsProvider = AnalyticsProvider;
|
|
@@ -8709,6 +9640,8 @@ exports.AutoSaveIndicator = AutoSaveIndicator;
|
|
|
8709
9640
|
exports.Badge = Badge;
|
|
8710
9641
|
exports.Button = Button;
|
|
8711
9642
|
exports.CalculatedFieldBuilder = CalculatedFieldBuilder;
|
|
9643
|
+
exports.ChatBubble = ChatBubble;
|
|
9644
|
+
exports.ChatPanel = ChatPanel;
|
|
8712
9645
|
exports.Checkbox = Checkbox;
|
|
8713
9646
|
exports.CollapsibleSection = CollapsibleSection;
|
|
8714
9647
|
exports.ColorPaletteSelector = ColorPaletteSelector;
|
|
@@ -8772,10 +9705,12 @@ exports.useDashboardMutations = useDashboardMutations;
|
|
|
8772
9705
|
exports.useDashboardPinStatus = useDashboardPinStatus;
|
|
8773
9706
|
exports.useDashboards = useDashboards;
|
|
8774
9707
|
exports.useDebouncedLayoutSave = useDebouncedLayoutSave;
|
|
9708
|
+
exports.useLLMChat = useLLMChat;
|
|
9709
|
+
exports.useLLMStatus = useLLMStatus;
|
|
8775
9710
|
exports.usePinMutations = usePinMutations;
|
|
8776
9711
|
exports.usePinnedDashboards = usePinnedDashboards;
|
|
8777
9712
|
exports.useQuery = useQuery;
|
|
8778
9713
|
exports.useSavedQueries = useSavedQueries;
|
|
8779
9714
|
exports.useSchema = useSchema;
|
|
8780
|
-
//# sourceMappingURL=chunk-
|
|
8781
|
-
//# sourceMappingURL=chunk-
|
|
9715
|
+
//# sourceMappingURL=chunk-PG7QBH3G.cjs.map
|
|
9716
|
+
//# sourceMappingURL=chunk-PG7QBH3G.cjs.map
|