@superatomai/sdk-node 0.0.6 → 0.0.8

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/index.mjs CHANGED
@@ -438,6 +438,7 @@ var require_main = __commonJS({
438
438
  // src/userResponse/utils.ts
439
439
  var utils_exports = {};
440
440
  __export(utils_exports, {
441
+ convertQuestionsToActions: () => convertQuestionsToActions,
441
442
  convertTopToLimit: () => convertTopToLimit,
442
443
  ensureQueryLimit: () => ensureQueryLimit,
443
444
  fixScalarSubqueries: () => fixScalarSubqueries,
@@ -539,6 +540,14 @@ function fixScalarSubqueries(query) {
539
540
  }
540
541
  return modifiedQuery;
541
542
  }
543
+ function convertQuestionsToActions(questions) {
544
+ return questions.map((question, index) => ({
545
+ id: `action_${index}_${Date.now()}`,
546
+ name: question,
547
+ type: "next_question",
548
+ question
549
+ }));
550
+ }
542
551
  function getJsonSizeInBytes(obj) {
543
552
  const jsonString = JSON.stringify(obj);
544
553
  return Buffer.byteLength(jsonString, "utf8");
@@ -841,7 +850,8 @@ var ComponentPropsSchema = z3.object({
841
850
  query: z3.string().optional(),
842
851
  title: z3.string().optional(),
843
852
  description: z3.string().optional(),
844
- config: z3.record(z3.unknown()).optional()
853
+ config: z3.record(z3.unknown()).optional(),
854
+ actions: z3.array(z3.any()).optional()
845
855
  });
846
856
  var ComponentSchema = z3.object({
847
857
  id: z3.string(),
@@ -1224,6 +1234,12 @@ var UIBlock = class {
1224
1234
  throw error;
1225
1235
  }
1226
1236
  }
1237
+ /**
1238
+ * Set or replace all actions
1239
+ */
1240
+ setActions(actions) {
1241
+ this.actions = actions;
1242
+ }
1227
1243
  /**
1228
1244
  * Add a single action (only if actions are resolved)
1229
1245
  */
@@ -2171,17 +2187,873 @@ var schema = new Schema();
2171
2187
  // src/userResponse/prompt-loader.ts
2172
2188
  import fs4 from "fs";
2173
2189
  import path3 from "path";
2190
+
2191
+ // src/userResponse/prompts.ts
2192
+ var PROMPTS = {
2193
+ "classify": {
2194
+ system: `You are an expert AI that classifies user questions about data and determines the appropriate visualizations needed.
2195
+
2196
+ CRITICAL: You MUST respond with ONLY valid JSON, no other text before or after.
2197
+
2198
+ ## Previous Conversation
2199
+ {{CONVERSATION_HISTORY}}
2200
+
2201
+ **Note:** If there is previous conversation history, use it to understand context. For example:
2202
+ - If user previously asked about "sales" and now asks "show me trends", understand it refers to sales trends
2203
+ - If user asked for "revenue by region" and now says "make it a pie chart", understand they want to modify the previous visualization
2204
+ - Use the history to resolve ambiguous references like "that", "it", "them", "the data"
2205
+
2206
+ Your task is to analyze the user's question and determine:
2207
+
2208
+ 1. **Question Type:**
2209
+ - "analytical": Questions asking to VIEW, ANALYZE, or VISUALIZE data
2210
+
2211
+ - "data_modification": Questions asking to CREATE, UPDATE, DELETE, or MODIFY data
2212
+
2213
+ - "general": General questions, greetings, or requests not related to data
2214
+
2215
+ 2. **Required Visualizations** (only for analytical questions):
2216
+ Determine which visualization type(s) would BEST answer the user's question:
2217
+
2218
+ - **KPICard**: Single metric, total, count, average, percentage, or summary number
2219
+
2220
+ - **LineChart**: Trends over time, time series, growth/decline patterns
2221
+
2222
+
2223
+ - **BarChart**: Comparing categories, rankings, distributions across groups
2224
+
2225
+
2226
+ - **PieChart**: Proportions, percentages, composition, market share
2227
+
2228
+
2229
+ - **DataTable**: Detailed lists, multiple attributes, when user needs to see records
2230
+
2231
+
2232
+ 3. **Multiple Visualizations:**
2233
+ User may need MULTIPLE visualizations together:
2234
+
2235
+ Common combinations:
2236
+ - KPICard + LineChart
2237
+ - KPICard + BarChart
2238
+ - KPICard + DataTable
2239
+ - BarChart + PieChart:
2240
+ - LineChart + DataTable
2241
+ Set needsMultipleComponents to true if user needs multiple views of the data.
2242
+
2243
+ **Important Guidelines:**
2244
+ - If user explicitly mentions a chart type RESPECT that preference
2245
+ - If question is vague or needs both summary and detail, suggest KPICard + DataTable
2246
+ - Only return visualizations for "analytical" questions
2247
+ - For "data_modification" or "general", return empty array for visualizations
2248
+
2249
+ **Output Format:**
2250
+ {
2251
+ "questionType": "analytical" | "data_modification" | "general",
2252
+ "visualizations": ["KPICard", "LineChart", ...], // Empty array if not analytical
2253
+ "reasoning": "Explanation of classification and visualization choices",
2254
+ "needsMultipleComponents": boolean
2255
+ }
2256
+ `,
2257
+ user: `{{USER_PROMPT}}
2258
+ `
2259
+ },
2260
+ "match-component": {
2261
+ system: `You are an expert AI assistant specialized in matching user requests to the most appropriate data visualization components.
2262
+
2263
+ CRITICAL: You MUST respond with ONLY valid JSON, no other text before or after.
2264
+
2265
+ ## Previous Conversation
2266
+ {{CONVERSATION_HISTORY}}
2267
+
2268
+ **Context Instructions:**
2269
+ - If there is conversation history, use it to understand what the user is referring to
2270
+ - When user says "show that as a chart" or "change it", they are referring to a previous component
2271
+ - If user asks to "modify" or "update" something, match to the component they previously saw
2272
+ - Use context to resolve ambiguous requests like "show trends for that" or "make it interactive"
2273
+
2274
+ Your task is to analyze the user's natural language request and find the BEST matching component from the available list.
2275
+
2276
+ Available Components:
2277
+ {{COMPONENTS_TEXT}}
2278
+
2279
+ **Matching Guidelines:**
2280
+
2281
+ 1. **Understand User Intent:**
2282
+ - What type of data visualization do they need? (KPI/metric, chart, table, etc.)
2283
+ - What metric or data are they asking about? (revenue, orders, customers, etc.)
2284
+ - Are they asking for a summary (KPI), trend (line chart), distribution (bar/pie), or detailed list (table)?
2285
+ - Do they want to compare categories, see trends over time, or show proportions?
2286
+
2287
+ 2. **Component Type Matching:**
2288
+ - KPICard: Single metric/number (total, average, count, percentage, rate)
2289
+ - LineChart: Trends over time, time series data
2290
+ - BarChart: Comparing categories, distributions, rankings
2291
+ - PieChart/DonutChart: Proportions, percentages, market share
2292
+ - DataTable: Detailed lists, rankings with multiple attributes
2293
+
2294
+ 3. **Keyword & Semantic Matching:**
2295
+ - Match user query terms with component keywords
2296
+ - Consider synonyms (e.g., "sales" = "revenue", "items" = "products")
2297
+ - Look for category matches (financial, orders, customers, products, suppliers, logistics, geographic, operations)
2298
+
2299
+ 4. **Scoring Criteria:**
2300
+ - Exact keyword matches: High priority
2301
+ - Component type alignment: High priority
2302
+ - Category alignment: Medium priority
2303
+ - Semantic similarity: Medium priority
2304
+ - Specificity: Prefer more specific components over generic ones
2305
+
2306
+ **Output Requirements:**
2307
+
2308
+ Respond with a JSON object containing:
2309
+ - componentIndex: the 1-based index of the BEST matching component (or null if confidence < 50%)
2310
+ - componentId: the ID of the matched component
2311
+ - reasoning: detailed explanation of why this component was chosen
2312
+ - confidence: confidence score 0-100 (100 = perfect match)
2313
+ - alternativeMatches: array of up to 2 alternative component indices with scores (optional)
2314
+
2315
+ Example response:
2316
+ {
2317
+ "componentIndex": 5,
2318
+ "componentId": "total_revenue_kpi",
2319
+ "reasoning": "User asks for 'total revenue' which perfectly matches the TotalRevenueKPI component (KPICard type) designed to show total revenue across all orders. Keywords match: 'total revenue', 'sales'.",
2320
+ "confidence": 95,
2321
+ "alternativeMatches": [
2322
+ {"index": 3, "id": "monthly_revenue_kpi", "score": 75, "reason": "Could show monthly revenue if time period was intended"},
2323
+ {"index": 8, "id": "revenue_trend_chart", "score": 60, "reason": "Could show revenue trend if historical view was intended"}
2324
+ ]
2325
+ }
2326
+
2327
+ **Important:**
2328
+ - Only return componentIndex if confidence >= 50%
2329
+ - Return null if no reasonable match exists
2330
+ - Prefer components that exactly match the user's metric over generic ones
2331
+ - Consider the full context of the request, not just individual words`,
2332
+ user: `Current user request: {{USER_PROMPT}}
2333
+
2334
+ Find the best matching component considering the conversation history above. Explain your reasoning with a confidence score. Return ONLY valid JSON.`
2335
+ },
2336
+ "modify-props": {
2337
+ system: `You are an AI assistant that validates and modifies component props based on user requests.
2338
+
2339
+ CRITICAL: You MUST respond with ONLY valid JSON, no other text before or after.
2340
+
2341
+ Given:
2342
+ - A user's natural language request
2343
+ - Component name: {{COMPONENT_NAME}}
2344
+ - Component type: {{COMPONENT_TYPE}}
2345
+ - Component description: {{COMPONENT_DESCRIPTION}}
2346
+
2347
+ -
2348
+ - Current component props with structure:
2349
+ {
2350
+ query?: string, // SQL query to fetch data
2351
+ title?: string, // Component title
2352
+ description?: string, // Component description
2353
+ config?: { // Additional configuration
2354
+ [key: string]: any
2355
+ }
2356
+ }
2357
+
2358
+ Schema definition for the prop that must be passed to the component
2359
+ -{{CURRENT_PROPS}}
2360
+
2361
+ Database Schema:
2362
+ {{SCHEMA_DOC}}
2363
+
2364
+ ## Previous Conversation
2365
+ {{CONVERSATION_HISTORY}}
2366
+
2367
+ **Context Instructions:**
2368
+ - Review the conversation history to understand the evolution of the component
2369
+ - If user says "add filter for X", understand they want to modify the current query
2370
+ - If user says "change to last month" or "filter by Y", apply modifications to existing query
2371
+ - Previous questions can clarify what the user means by ambiguous requests like "change that filter"
2372
+ - Use context to determine appropriate time ranges if user says "recent" or "latest"
2373
+
2374
+ Your task is to intelligently modify the props based on the user's request:
2375
+
2376
+ 1. **Query Modification**:
2377
+ - Modify SQL query if user requests different data, filters, time ranges, limits, or aggregations
2378
+ - Use correct table and column names from the schema
2379
+ - Ensure valid SQL syntax
2380
+ - ALWAYS include a LIMIT clause (default: {{DEFAULT_LIMIT}} rows) to prevent large result sets
2381
+ - Preserve the query structure that the component expects (e.g., column aliases)
2382
+
2383
+ **CRITICAL - PostgreSQL Query Rules:**
2384
+
2385
+ **NO AGGREGATE FUNCTIONS IN WHERE CLAUSE:**
2386
+ \u274C WRONG: \`WHERE COUNT(orders) > 0\` or \`WHERE SUM(price) > 100\`
2387
+ \u2705 CORRECT: Use HAVING (with GROUP BY), EXISTS, or subquery
2388
+
2389
+
2390
+ **WHERE vs HAVING:**
2391
+ - WHERE filters rows BEFORE grouping (cannot use aggregates)
2392
+ - HAVING filters groups AFTER grouping (can use aggregates)
2393
+ - If using HAVING, you MUST have GROUP BY
2394
+
2395
+ **Subquery Rules:**
2396
+ - When using a subquery with scalar comparison operators (=, <, >, <=, >=, <>), the subquery MUST return exactly ONE row
2397
+ - ALWAYS add \`LIMIT 1\` to scalar subqueries to prevent "more than one row returned" errors
2398
+ - Example: \`WHERE location_id = (SELECT store_id FROM orders ORDER BY total_amount DESC LIMIT 1)\`
2399
+ - For multiple values, use \`IN\` instead: \`WHERE location_id IN (SELECT store_id FROM orders)\`
2400
+ - Test your subqueries mentally: if they could return multiple rows, add LIMIT 1 or use IN
2401
+
2402
+ 2. **Title Modification**:
2403
+ - Update title to reflect the user's specific request
2404
+ - Keep it concise and descriptive
2405
+ - Match the tone of the original title
2406
+
2407
+ 3. **Description Modification**:
2408
+ - Update description to explain what data is shown
2409
+ - Be specific about filters, time ranges, or groupings applied
2410
+
2411
+ 4. **Config Modification** (based on component type):
2412
+ - For KPICard: formatter, gradient, icon
2413
+ - For Charts: colors, height, xKey, yKey, nameKey, valueKey
2414
+ - For Tables: columns, pageSize, formatters
2415
+
2416
+
2417
+ Respond with a JSON object:
2418
+ {
2419
+ "props": { /* modified props object with query, title, description, config */ },
2420
+ "isModified": boolean,
2421
+ "reasoning": "brief explanation of changes",
2422
+ "modifications": ["list of specific changes made"]
2423
+ }
2424
+
2425
+ IMPORTANT:
2426
+ - Return the COMPLETE props object, not just modified fields
2427
+ - Preserve the structure expected by the component type
2428
+ - Ensure query returns columns with expected aliases
2429
+ - Keep config properties that aren't affected by the request`,
2430
+ user: `{{USER_PROMPT}}`
2431
+ },
2432
+ "single-component": {
2433
+ system: `You are an expert AI assistant specialized in matching user requests to the most appropriate component from a filtered list.
2434
+
2435
+ CRITICAL: You MUST respond with ONLY valid JSON, no other text before or after.
2436
+
2437
+
2438
+ ## Previous Conversation
2439
+ {{CONVERSATION_HISTORY}}
2440
+
2441
+ **Context Instructions:**
2442
+ - If there is previous conversation history, use it to understand what the user is referring to
2443
+ - When user says "show trends", "add filters", "change that", understand they may be building on previous queries
2444
+ - Use previous component types and queries as context to inform your current matching
2445
+
2446
+ ## Available Components (Type: {{COMPONENT_TYPE}})
2447
+ The following components have been filtered by type {{COMPONENT_TYPE}}. Select the BEST matching one:
2448
+
2449
+ {{COMPONENTS_LIST}}
2450
+
2451
+ {{VISUALIZATION_CONSTRAINT}}
2452
+
2453
+ **Select the BEST matching component** from the available {{COMPONENT_TYPE}} components listed above that would best answer the user's question.
2454
+
2455
+ **Matching Guidelines:**
2456
+ 1. **Semantic Matching:**
2457
+ - Match based on component name, description, and keywords
2458
+ - Consider what metrics/data the user is asking about
2459
+ - Look for semantic similarity (e.g., "sales" matches "revenue", "orders" matches "purchases")
2460
+
2461
+ 2. **Query Relevance:**
2462
+ - Consider the component's existing query structure
2463
+ - Does it query the right tables/columns for the user's question?
2464
+ - Can it be modified to answer the user's specific question?
2465
+
2466
+ 3. **Scoring Criteria:**
2467
+ - Exact keyword matches in name/description: High priority
2468
+ - Semantic similarity to user intent: High priority
2469
+ - Appropriate aggregation/grouping: Medium priority
2470
+ - Category alignment: Medium priority
2471
+
2472
+ **Output Requirements:**
2473
+
2474
+ Respond with a JSON object:
2475
+ {
2476
+ "componentId": "matched_component_id",
2477
+ "componentIndex": 1, // 1-based index from the filtered list above
2478
+ "reasoning": "Detailed explanation of why this component best matches the user's question",
2479
+ "confidence": 85, // Confidence score 0-100
2480
+ "canGenerate": true // false if no suitable component found (confidence < 50)
2481
+ }
2482
+
2483
+ **Important:**
2484
+ - Only set canGenerate to true if confidence >= 50%
2485
+ - If no component from the list matches well (all have low relevance), set canGenerate to false
2486
+ - Consider the full context of the request and conversation history
2487
+ - The component's props (query, title, description, config) will be modified later based on the user's specific request
2488
+ - Focus on finding the component that is closest to what the user needs, even if it needs modification`,
2489
+ user: `{{USER_PROMPT}}
2490
+
2491
+ `
2492
+ },
2493
+ "mutli-component": {
2494
+ system: `You are an expert data analyst AI that creates comprehensive multi-component analytical dashboards with aesthetically pleasing and balanced layouts.
2495
+
2496
+ CRITICAL: You MUST respond with ONLY valid JSON, no other text before or after.
2497
+
2498
+ Database Schema:
2499
+ {{SCHEMA_DOC}}
2500
+
2501
+ ## Previous Conversation
2502
+ {{CONVERSATION_HISTORY}}
2503
+
2504
+ **Context Instructions:**
2505
+ - Review the conversation history to understand what the user has asked before
2506
+ - If user is building on previous insights (e.g., "now show me X and Y"), use context to inform dashboard design
2507
+ - Previous queries can help determine appropriate filters, date ranges, or categories to use
2508
+ - If user asks for "comprehensive view" or "dashboard for X", include complementary components based on context
2509
+
2510
+ Given a user's analytical question and the required visualization types, your task is to:
2511
+
2512
+ 1. **Determine Container Metadata:**
2513
+ - title: Clear, descriptive title for the entire dashboard (2-5 words)
2514
+ - description: Brief explanation of what insights this dashboard provides (1-2 sentences)
2515
+
2516
+ 2. **Generate Props for Each Component:**
2517
+ For each visualization type requested, create tailored props:
2518
+
2519
+ - **query**: SQL query specific to this visualization using the database schema
2520
+ * Use correct table and column names
2521
+ * **DO NOT USE TOP keyword - use LIMIT instead (e.g., LIMIT 20, not TOP 20)**
2522
+ * ALWAYS include LIMIT clause ONCE at the end (default: {{DEFAULT_LIMIT}})
2523
+ * For KPICard: Return single row with column alias "value"
2524
+ * For Charts: Return appropriate columns (name/label and value, or x and y)
2525
+ * For Table: Return relevant columns
2526
+
2527
+ - **title**: Specific title for this component (2-4 words)
2528
+
2529
+ - **description**: What this specific component shows (1 sentence)
2530
+
2531
+ - **config**: Type-specific configuration
2532
+ * KPICard: { gradient, formatter, icon }
2533
+ * BarChart: { xKey, yKey, colors, height }
2534
+ * LineChart: { xKey, yKeys, colors, height }
2535
+ * PieChart: { nameKey, valueKey, colors, height }
2536
+ * DataTable: { pageSize }
2537
+
2538
+ 3. **CRITICAL: Component Hierarchy and Ordering:**
2539
+ The ORDER of components in the array MUST follow this STRICT hierarchy for proper visual layout:
2540
+
2541
+ **HIERARCHY RULES (MUST FOLLOW IN THIS ORDER):**
2542
+ 1. KPICards - ALWAYS FIRST (top of dashboard for summary metrics)
2543
+ 2. Charts/Graphs - AFTER KPICards (middle of dashboard for visualizations)
2544
+ * BarChart, LineChart, PieChart, DonutChart
2545
+ 3. DataTable - ALWAYS LAST (bottom of dashboard, full width for detailed data)
2546
+
2547
+ **LAYOUT BEHAVIOR (Frontend enforces):**
2548
+ - KPICards: Display in responsive grid (3 columns)
2549
+ - Single Chart (if only 1 chart): Takes FULL WIDTH
2550
+ - Multiple Charts (if 2+ charts): Display in 2-column grid
2551
+ - DataTable (if present): Always spans FULL WIDTH at bottom
2552
+
2553
+
2554
+ **ABSOLUTELY DO NOT deviate from this hierarchy. Always place:**
2555
+ - KPICards first
2556
+ - Charts/Graphs second
2557
+ - DataTable last (if present)
2558
+
2559
+ **Important Guidelines:**
2560
+ - Each component should answer a DIFFERENT aspect of the user's question
2561
+ - Queries should be complementary, not duplicated
2562
+ - If user asks "Show total revenue and trend", generate:
2563
+ * KPICard: Single total value (FIRST)
2564
+ * LineChart: Revenue over time (SECOND)
2565
+ - Ensure queries use valid columns from the schema
2566
+ - Make titles descriptive and specific to what each component shows
2567
+ - **Snowflake Syntax MUST be used:**
2568
+ * Use LIMIT (not TOP)
2569
+ * Use DATE_TRUNC, DATEDIFF (not DATEPART)
2570
+ * Include LIMIT only ONCE per query at the end
2571
+
2572
+ **Output Format:**
2573
+ {
2574
+ "containerTitle": "Dashboard Title",
2575
+ "containerDescription": "Brief description of the dashboard insights",
2576
+ "components": [
2577
+ {
2578
+ "componentType": "KPICard" | "BarChart" | "LineChart" | "PieChart" | "DataTable",
2579
+ "query": "SQL query",
2580
+ "title": "Component title",
2581
+ "description": "Component description",
2582
+ "config": { /* type-specific config */ }
2583
+ },
2584
+ ...
2585
+ ],
2586
+ "reasoning": "Explanation of the dashboard design and component ordering",
2587
+ "canGenerate": boolean
2588
+ }`,
2589
+ user: `Current user question: {{USER_PROMPT}}
2590
+
2591
+ Required visualization types: {{VISUALIZATION_TYPES}}
2592
+
2593
+ Generate a complete multi-component dashboard with appropriate container metadata and tailored props for each component. Consider the conversation history above when designing the dashboard. Return ONLY valid JSON.`
2594
+ },
2595
+ "container-metadata": {
2596
+ system: `You are an expert AI assistant that generates titles and descriptions for multi-component dashboards.
2597
+
2598
+ CRITICAL: You MUST respond with ONLY valid JSON, no other text before or after.
2599
+
2600
+ ## Previous Conversation
2601
+ {{CONVERSATION_HISTORY}}
2602
+
2603
+ **Context Instructions:**
2604
+ - If there is previous conversation history, use it to understand what the user is referring to
2605
+ - Use context to create relevant titles and descriptions that align with the user's intent
2606
+
2607
+ Your task is to generate a concise title and description for a multi-component dashboard that will contain the following visualization types:
2608
+ {{VISUALIZATION_TYPES}}
2609
+
2610
+ **Guidelines:**
2611
+
2612
+ 1. **Title:**
2613
+ - Should be clear and descriptive (3-8 words)
2614
+ - Should reflect what the user is asking about
2615
+ - Should NOT include "Dashboard" suffix (that will be added automatically)
2616
+
2617
+ 2. **Description:**
2618
+ - Should be a brief summary (1-2 sentences)
2619
+ - Should explain what insights the dashboard provides
2620
+
2621
+ **Output Requirements:**
2622
+
2623
+ Respond with a JSON object:
2624
+ {
2625
+ "title": "Dashboard title without 'Dashboard' suffix",
2626
+ "description": "Brief description of what this dashboard shows"
2627
+ }
2628
+
2629
+ **Important:**
2630
+ - Keep the title concise and meaningful
2631
+ - Make the description informative but brief
2632
+ - Focus on what insights the user will gain
2633
+ `,
2634
+ user: `{{USER_PROMPT}}
2635
+ `
2636
+ },
2637
+ "text-response": {
2638
+ system: `You are an intelligent AI assistant that provides helpful, accurate, and contextual text responses to user questions.
2639
+
2640
+ ## Your Task
2641
+
2642
+ Analyze the user's question and provide a helpful text response. Your response should:
2643
+
2644
+ 1. **Be Clear and Concise**: Provide direct answers without unnecessary verbosity
2645
+ 2. **Be Contextual**: Use conversation history to understand what the user is asking about
2646
+ 3. **Be Accurate**: Provide factually correct information based on the context
2647
+ 4. **Be Helpful**: Offer additional relevant information or suggestions when appropriate
2648
+
2649
+ ## Handling Data Questions
2650
+
2651
+ When the user asks about data
2652
+
2653
+ 1. **Generate a SQL query** using the database schema provided above
2654
+ 2. **Use the execute_query tool** to run the query
2655
+ 3. **If the query fails**, analyze the error and generate a corrected query
2656
+ 4. **Format the results** in a clear, readable way for the user
2657
+
2658
+ **Query Guidelines:**
2659
+ - Use correct table and column names from the schema
2660
+ - ALWAYS include a LIMIT clause with a MAXIMUM of 32 rows
2661
+ - Ensure valid SQL syntax
2662
+ - For time-based queries, use appropriate date functions
2663
+ - When using subqueries with scalar operators (=, <, >, etc.), add LIMIT 1 to prevent "more than one row" errors
2664
+
2665
+ ## Response Guidelines
2666
+
2667
+ - If the question is about data, use the execute_query tool to fetch data and present it
2668
+ - If the question is general knowledge, provide a helpful conversational response
2669
+ - If asking for clarification, provide options or ask specific follow-up questions
2670
+ - If you don't have enough information, acknowledge it and ask for more details
2671
+ - Keep responses focused and avoid going off-topic
2672
+
2673
+ ## Component Suggestions
2674
+
2675
+ After analyzing the query results, you MUST suggest appropriate dashboard components for displaying the data. Use this format:
2676
+
2677
+ **Dashboard Components:**
2678
+ Format: \`{number}.{component_type} : {clear reasoning}\`
2679
+
2680
+
2681
+ **Rules for component suggestions:**
2682
+ 1. Analyze the query results structure and data type
2683
+ 2. Suggest components that would best visualize the data
2684
+ 3. Each component suggestion must be on a new line
2685
+
2686
+
2687
+ IMPORTANT: Always include the **Dashboard Components:** section with at least one component suggestion when data is returned.
2688
+
2689
+ ## Output Format
2690
+
2691
+ Respond with plain text that includes:
2692
+
2693
+ 1. **Query Analysis** (if applicable): Brief explanation of what data was fetched
2694
+ 2. **Results Summary**: Present the data in a clear, readable format
2695
+ 3. **Dashboard Components**: List suggested components in the c1:type format
2696
+
2697
+
2698
+ **CRITICAL:**
2699
+ - Return ONLY plain text (no JSON, no markdown code blocks)
2700
+
2701
+
2702
+ You have access to a database and can execute SQL queries to answer data-related questions.
2703
+ ## Database Schema
2704
+ {{SCHEMA_DOC}}
2705
+
2706
+ **Database Type: PostgreSQL**
2707
+
2708
+ **CRITICAL PostgreSQL Query Rules:**
2709
+
2710
+ 1. **NO AGGREGATE FUNCTIONS IN WHERE CLAUSE** - This is a fundamental SQL error
2711
+ \u274C WRONG: \`WHERE COUNT(orders) > 0\`
2712
+ \u274C WRONG: \`WHERE SUM(price) > 100\`
2713
+ \u274C WRONG: \`WHERE AVG(rating) > 4.5\`
2714
+
2715
+ \u2705 CORRECT: Use HAVING (with GROUP BY), EXISTS, or subquery
2716
+
2717
+ 2. **WHERE vs HAVING**
2718
+ - WHERE filters rows BEFORE grouping (cannot use aggregates)
2719
+ - HAVING filters groups AFTER grouping (can use aggregates)
2720
+ - If using HAVING, you MUST have GROUP BY
2721
+
2722
+ 3. **NO NESTED AGGREGATE FUNCTIONS** - PostgreSQL does NOT allow aggregates inside aggregates
2723
+ \u274C WRONG: \`AVG(ROUND(AVG(column), 2))\` or \`SELECT AVG(SUM(price)) FROM ...\`
2724
+ \u2705 CORRECT: \`ROUND(AVG(column), 2)\`
2725
+
2726
+ 4. **GROUP BY Requirements**
2727
+ - ALL non-aggregated columns in SELECT must be in GROUP BY
2728
+ - If you SELECT a column and don't aggregate it, add it to GROUP BY
2729
+
2730
+ 5. **LIMIT Clause**
2731
+ - ALWAYS include LIMIT (max 32 rows)
2732
+ - For scalar subqueries in WHERE/HAVING, add LIMIT 1
2733
+
2734
+
2735
+ ## Knowledge Base Context
2736
+
2737
+ The following relevant information has been retrieved from the knowledge base for this question:
2738
+
2739
+ {{KNOWLEDGE_BASE_CONTEXT}}
2740
+
2741
+ Use this knowledge base information to:
2742
+ - Provide more accurate and informed responses
2743
+ - Reference specific facts, concepts, or domain knowledge
2744
+ - Enhance your understanding of the user's question
2745
+ - Give context-aware recommendations
2746
+
2747
+ **Note:** If there is previous conversation history, use it to understand context and provide coherent responses:
2748
+ - Reference previous questions and answers when relevant
2749
+ - Maintain consistency with earlier responses
2750
+ - Use the history to resolve ambiguous references like "that", "it", "them", "the previous data"
2751
+
2752
+ ## Previous Conversation
2753
+ {{CONVERSATION_HISTORY}}
2754
+
2755
+
2756
+ `,
2757
+ user: `{{USER_PROMPT}}
2758
+
2759
+ `
2760
+ },
2761
+ "match-text-components": {
2762
+ system: `You are a component matching expert that creates beautiful, well-structured dashboard visualizations from analysis results.
2763
+
2764
+ ## Your Task
2765
+
2766
+ You will receive a text response containing:
2767
+ 1. Query execution results (with "\u2705 Query executed successfully!" markers)
2768
+ 2. Data analysis and insights
2769
+ 3. **Dashboard Components:** suggestions (1:component_type : reasoning format)
2770
+
2771
+ Your job is to:
2772
+ 1. **DISCOVER and SELECT the best dashboard layout component** from the available components list
2773
+ 2. Parse the component suggestions from the text response
2774
+ 3. Match each suggestion with an actual component from the available list
2775
+ 4. Generate the RIGHT NUMBER of components to fit the selected layout perfectly (based on the layout component's description)
2776
+ 5. Generate proper props to **visualize the analysis results** that were already fetched
2777
+ 6. **Generate intelligent follow-up questions (actions)** that the user might naturally ask next based on the data analysis
2778
+
2779
+ **CRITICAL GOAL**: Create dashboard components that display the **same data that was already analyzed** - NOT new data. The queries already ran and got results. You're just creating different visualizations of those results.
2780
+
2781
+
2782
+
2783
+ ## Layout Component Discovery
2784
+ **Find layout components** by looking for components with \`type: "DashboardLayout"\`
2785
+
2786
+ ## Layout Selection Logic
2787
+ 1. **Read each layout's description** to understand:
2788
+ - What structure it provides
2789
+ - When it's best used (e.g., comprehensive analysis vs focused analysis)
2790
+ - The exact number and types of components it requires
2791
+ 2. **Select the best layout** based on:
2792
+ - Which layout structure best fits the analysis needs
2793
+ - The component suggestions from the text response
2794
+ - The user question and data complexity
2795
+ 3. **Generate EXACTLY the components specified** in the selected layout's description
2796
+
2797
+ **IMPORTANT:** Always prefer structured dashboard layouts when available. The layout component's description will tell you exactly what components to generate.
2798
+
2799
+ ## Available Components
2800
+
2801
+ {{AVAILABLE_COMPONENTS}}
2802
+
2803
+ ## Component Matching Rules
2804
+ For each component suggestion (c1, c2, c3, etc.):
2805
+
2806
+ 1. **Match by type**: Find components whose \`type\` matches the suggested component type
2807
+ 2. **Refine by relevance**: If multiple components match, choose based on:
2808
+ - Description and keywords matching the use case
2809
+ - Best fit for the data being visualized
2810
+ 3. **Fallback**: If no exact type match, find the closest alternative
2811
+
2812
+ ## Props Generation Rules
2813
+
2814
+ For each matched component, generate complete props:
2815
+
2816
+ ### 1. Query - When to Reuse vs Generate
2817
+
2818
+ **Option A: REUSE the successful query** (preferred when possible)
2819
+ - Look for "\u2705 Query executed successfully!" in the text response
2820
+ - Extract the exact SQL query that worked
2821
+ - Use this SAME query for components that visualize the same data differently
2822
+
2823
+ **Option B: GENERATE a new query** (when necessary)
2824
+ - Only generate new queries when you need DIFFERENT data
2825
+ - Use the database schema below to write valid SQL
2826
+
2827
+
2828
+ **Decision Logic:**
2829
+ - Different aggregation needed \u2192 Generate new query \u2705
2830
+ - Same data, different view \u2192 Reuse query \u2705
2831
+
2832
+ **Database Schema:**
2833
+ {{SCHEMA_DOC}}
2834
+
2835
+ **Database Type: PostgreSQL**
2836
+
2837
+ **CRITICAL PostgreSQL Query Rules**
2838
+
2839
+ 1. **NO AGGREGATE FUNCTIONS IN WHERE CLAUSE** - This is a fundamental SQL error
2840
+ \u274C WRONG: \`WHERE COUNT(orders) > 0\`
2841
+ \u274C WRONG: \`WHERE SUM(price) > 100\`
2842
+ \u274C WRONG: \`WHERE AVG(rating) > 4.5\`
2843
+
2844
+ \u2705 CORRECT: Use HAVING (with GROUP BY), EXISTS, or subquery
2845
+
2846
+ 2. **NO NESTED AGGREGATE FUNCTIONS** - PostgreSQL does NOT allow aggregates inside aggregates
2847
+ \u274C WRONG: \`AVG(ROUND(AVG(column), 2))\`
2848
+ \u2705 CORRECT: \`ROUND(AVG(column), 2)\`
2849
+
2850
+ 3. **Aggregate Functions Can Only Appear Once Per Level**
2851
+ \u274C WRONG: \`SELECT AVG(SUM(price)) FROM ...\` (nested)
2852
+ \u2705 CORRECT: Use subquery: \`SELECT AVG(total) FROM (SELECT SUM(price) as total FROM ... GROUP BY ...) subq\`
2853
+
2854
+ 4. **WHERE vs HAVING**
2855
+ - WHERE filters rows BEFORE grouping (cannot use aggregates)
2856
+ - HAVING filters groups AFTER grouping (can use aggregates)
2857
+ - Use WHERE for column comparisons: \`WHERE price > 100\`
2858
+ - Use HAVING for aggregate comparisons: \`HAVING COUNT(*) > 5\`
2859
+
2860
+ 5. **GROUP BY Requirements**
2861
+ - ALL non-aggregated columns in SELECT must be in GROUP BY
2862
+ - Use proper column references (table.column or aliases)
2863
+ - If using HAVING, you MUST have GROUP BY
2864
+
2865
+ 6. **LIMIT Clause**
2866
+ - ALWAYS include LIMIT clause (max 32 rows for dashboard queries)
2867
+ - For scalar subqueries in WHERE/HAVING, add LIMIT 1
2868
+
2869
+ 7. **Scalar Subqueries**
2870
+ - Subqueries used with =, <, >, etc. must return single value
2871
+ - Always add LIMIT 1 to scalar subqueries
2872
+
2873
+ **Query Generation Guidelines** (when creating new queries):
2874
+ - Use correct table and column names from the schema above
2875
+ - ALWAYS include LIMIT clause (max 32 rows)
2876
+
2877
+ ### 2. Title
2878
+ - Create a clear, descriptive title
2879
+ - Should explain what the component shows
2880
+ - Use context from the original question
2881
+
2882
+ ### 3. Description
2883
+ - Brief explanation of what this component displays
2884
+ - Why it's useful for this data
2885
+
2886
+ ### 4. Config
2887
+ - **CRITICAL**: Look at the component's "Props Structure" to see what config fields it expects
2888
+ - Map query result columns to the appropriate config fields
2889
+ - Keep other existing config properties that don't need to change
2890
+ - Ensure config field values match actual column names from the query
2891
+
2892
+ **Special Rules for Bar Charts**
2893
+ - \`xAxisKey\` = ALWAYS the category/label column (text field )
2894
+ - \`yAxisKey\` = ALWAYS the numeric value column (number field )
2895
+ - \`orientation\` = "vertical" or "horizontal" (controls visual direction only)
2896
+ - **DO NOT swap xAxisKey/yAxisKey based on orientation** - they always represent category and value respectively
2897
+
2898
+ ## Follow-Up Questions (Actions) Generation
2899
+
2900
+ After analyzing the text response and matched components, generate 4-5 intelligent follow-up questions that the user might naturally ask next. These questions should:
2901
+
2902
+ 1. **Build upon the data analysis** shown in the text response and components
2903
+ 2. **Explore natural next steps** in the data exploration journey
2904
+ 3. **Be progressively more detailed or specific** - go deeper into the analysis
2905
+ 4. **Consider the insights revealed** - suggest questions that help users understand implications
2906
+ 5. **Be phrased naturally** as if a real user would ask them
2907
+ 6. **Vary in scope** - include both broad trends and specific details
2908
+ 7. **Avoid redundancy** - don't ask questions already answered in the text response
2909
+
2910
+
2911
+ ## Output Format
2912
+
2913
+ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
2914
+
2915
+ **IMPORTANT JSON FORMATTING RULES:**
2916
+ - Put SQL queries on a SINGLE LINE (no newlines in the query string)
2917
+ - Escape all quotes in SQL properly (use \\" for quotes inside strings)
2918
+ - Remove any newlines, tabs, or special characters from SQL
2919
+ - Do NOT use markdown code blocks (no \`\`\`)
2920
+ - Return ONLY the JSON object, nothing else
2921
+
2922
+ \`\`\`json
2923
+ {
2924
+ "selectedLayout": "Name of the selected layout component from available components",
2925
+ "selectedLayoutId": "id_of_the_selected_layout_component",
2926
+ "layoutReasoning": "Why this layout was selected based on its description and the analysis needs",
2927
+ "matchedComponents": [
2928
+ {
2929
+ "componentId": "id_from_available_list",
2930
+ "componentName": "name_of_component",
2931
+ "componentType": "type_of_component",
2932
+ "reasoning": "Why this component was selected for this suggestion",
2933
+ "originalSuggestion": "c1:table : original reasoning from text",
2934
+ "props": {
2935
+ "query": "SQL query for this component",
2936
+ "title": "Component title",
2937
+ "description": "Component description",
2938
+ "config": {
2939
+ "field1": "value1",
2940
+ "field2": "value2"
2941
+ }
2942
+ }
2943
+ }
2944
+ ],
2945
+ "actions": [
2946
+ "Follow-up question 1?",
2947
+ "Follow-up question 2?",
2948
+ "Follow-up question 3?",
2949
+ "Follow-up question 4?",
2950
+ "Follow-up question 5?"
2951
+ ]
2952
+ }
2953
+ \`\`\`
2954
+
2955
+ **CRITICAL:**
2956
+ - \`selectedLayout\` MUST be the NAME of a layout component from the available components list (must have type "DashboardLayout")
2957
+ - \`selectedLayoutId\` MUST be the ID of the selected layout component
2958
+ - \`layoutReasoning\` MUST explain:
2959
+ - Why you chose this specific layout component
2960
+ - How its structure (from its description) matches the analysis needs
2961
+ - What components it requires based on its description
2962
+ - \`matchedComponents\` array length and types MUST match what the selected layout's description specifies
2963
+ - \`actions\` MUST be an array of 4-5 intelligent follow-up questions based on the analysis
2964
+ - Return ONLY valid JSON (no markdown code blocks, no text before/after)
2965
+ - Generate complete props for each component
2966
+
2967
+
2968
+ `,
2969
+ user: `## Text Response
2970
+
2971
+ {{TEXT_RESPONSE}}
2972
+
2973
+ ---
2974
+
2975
+ Match the component suggestions from the text response above with available components, and generate proper props for each matched component.
2976
+ `
2977
+ },
2978
+ "actions": {
2979
+ system: `You are an expert data analyst. Your task is to suggest intelligent follow-up questions based on the conversation history and the current data visualization. The questions should help users explore the data more deeply and make better insights.
2980
+
2981
+ ## Previous Conversation
2982
+ {{CONVERSATION_HISTORY}}
2983
+
2984
+ **Context Instructions:**
2985
+ - Review the entire conversation history to understand the user's journey and interests
2986
+ - Suggest questions that naturally progress from previous explorations
2987
+ - Avoid suggesting questions about topics already covered in the conversation
2988
+ - Build upon insights from previous components to suggest deeper analysis
2989
+ - Consider what the user might want to explore next based on their question pattern`,
2990
+ user: `Given the following context:
2991
+
2992
+ Latest User Question: {{ORIGINAL_USER_PROMPT}}
2993
+
2994
+ Current Component:
2995
+ {{COMPONENT_INFO}}
2996
+
2997
+ {{COMPONENT_DATA}}
2998
+
2999
+ Generate a JSON array of 4-5 intelligent follow-up questions that the user might naturally ask next. These questions should:
3000
+ 1. Build upon the conversation history and insights shown in the current component
3001
+ 2. NOT repeat questions or topics already covered in the conversation history
3002
+ 3. Explore natural next steps in the data exploration journey
3003
+ 4. Be progressively more detailed or specific
3004
+ 5. Consider the user's apparent interests based on their question pattern
3005
+ 6. Be phrased naturally as if a real user would ask them
3006
+
3007
+ Format your response as a JSON object with this structure:
3008
+ {
3009
+ "nextQuestions": [
3010
+ "Question 1?",
3011
+ "Question 2?",
3012
+ ...
3013
+ ]
3014
+ }
3015
+
3016
+ Return ONLY valid JSON.`
3017
+ }
3018
+ };
3019
+
3020
+ // src/userResponse/prompt-loader.ts
2174
3021
  var PromptLoader = class {
2175
3022
  constructor(config) {
2176
3023
  this.promptCache = /* @__PURE__ */ new Map();
2177
3024
  this.isInitialized = false;
2178
- logger.debug("Initializing PromptLoader...", process.cwd());
3025
+ logger.debug("Initializing PromptLoader...");
2179
3026
  this.promptsDir = config?.promptsDir || path3.join(process.cwd(), ".prompts");
2180
- this.defaultPromptsDir = path3.join(__dirname, "..", "..", ".prompts");
3027
+ logger.debug(`Prompts directory set to: ${this.promptsDir}`);
3028
+ }
3029
+ /**
3030
+ * Load a prompt template from file system OR fallback to hardcoded prompts
3031
+ * @param promptName - Name of the prompt folder
3032
+ * @returns Template with system and user prompts
3033
+ */
3034
+ loadPromptTemplate(promptName) {
3035
+ try {
3036
+ const systemPath = path3.join(this.promptsDir, promptName, "system.md");
3037
+ const userPath = path3.join(this.promptsDir, promptName, "user.md");
3038
+ if (fs4.existsSync(systemPath) && fs4.existsSync(userPath)) {
3039
+ const system = fs4.readFileSync(systemPath, "utf-8");
3040
+ const user = fs4.readFileSync(userPath, "utf-8");
3041
+ logger.info(`\u2713 Loaded prompt '${promptName}' from file system: ${this.promptsDir}`);
3042
+ return { system, user };
3043
+ }
3044
+ } catch (error) {
3045
+ logger.error(`Could not load '${promptName}' from file system, trying fallback...`);
3046
+ }
3047
+ const hardcodedPrompt = PROMPTS[promptName];
3048
+ if (hardcodedPrompt) {
3049
+ logger.info(`\u2713 Loaded prompt '${promptName}' from hardcoded fallback`);
3050
+ return hardcodedPrompt;
3051
+ }
3052
+ throw new Error(`Prompt template '${promptName}' not found in either ${this.promptsDir} or hardcoded prompts. Available prompts: ${Object.keys(PROMPTS).join(", ")}`);
2181
3053
  }
2182
3054
  /**
2183
3055
  * Initialize and cache all prompts into memory
2184
- * This should be called once at SDK startup
3056
+ * Tries file system first, then falls back to hardcoded prompts
2185
3057
  */
2186
3058
  async initialize() {
2187
3059
  if (this.isInitialized) {
@@ -2189,61 +3061,19 @@ var PromptLoader = class {
2189
3061
  return;
2190
3062
  }
2191
3063
  logger.info("Loading prompts into memory...");
2192
- const promptTypes = [
2193
- "classify",
2194
- "match-component",
2195
- "modify-props",
2196
- "single-component",
2197
- "mutli-component",
2198
- "actions",
2199
- "container-metadata",
2200
- "text-response",
2201
- "match-text-components"
2202
- ];
2203
- for (const promptType of promptTypes) {
3064
+ const promptTypes = Object.keys(PROMPTS);
3065
+ for (const promptName of promptTypes) {
2204
3066
  try {
2205
- const template = await this.loadPromptTemplate(promptType);
2206
- this.promptCache.set(promptType, template);
2207
- logger.debug(`Cached prompt: ${promptType}`);
3067
+ const template = this.loadPromptTemplate(promptName);
3068
+ this.promptCache.set(promptName, template);
2208
3069
  } catch (error) {
2209
- logger.error(`Failed to load prompt '${promptType}':`, error);
3070
+ logger.error(`Failed to load prompt '${promptName}':`, error);
2210
3071
  throw error;
2211
3072
  }
2212
3073
  }
2213
3074
  this.isInitialized = true;
2214
3075
  logger.info(`Successfully loaded ${this.promptCache.size} prompt templates into memory`);
2215
3076
  }
2216
- /**
2217
- * Load a prompt template from file system (tries custom dir first, then defaults to SDK dir)
2218
- * @param promptName - Name of the prompt folder
2219
- * @returns Template with system and user prompts
2220
- */
2221
- async loadPromptTemplate(promptName) {
2222
- const tryLoadFromDir = (dir) => {
2223
- try {
2224
- const systemPath = path3.join(dir, promptName, "system.md");
2225
- const userPath = path3.join(dir, promptName, "user.md");
2226
- if (fs4.existsSync(systemPath) && fs4.existsSync(userPath)) {
2227
- const system = fs4.readFileSync(systemPath, "utf-8");
2228
- const user = fs4.readFileSync(userPath, "utf-8");
2229
- logger.debug(`Loaded prompt '${promptName}' from ${dir}`);
2230
- return { system, user };
2231
- }
2232
- return null;
2233
- } catch (error) {
2234
- return null;
2235
- }
2236
- };
2237
- let template = tryLoadFromDir(this.promptsDir);
2238
- if (!template) {
2239
- logger.warn(`Prompt '${promptName}' not found in ${this.promptsDir}, trying default location...`);
2240
- template = tryLoadFromDir(this.defaultPromptsDir);
2241
- }
2242
- if (!template) {
2243
- throw new Error(`Prompt template '${promptName}' not found in either ${this.promptsDir} or ${this.defaultPromptsDir}`);
2244
- }
2245
- return template;
2246
- }
2247
3077
  /**
2248
3078
  * Replace variables in a template string using {{VARIABLE_NAME}} pattern
2249
3079
  * @param template - Template string with placeholders
@@ -2261,13 +3091,13 @@ var PromptLoader = class {
2261
3091
  }
2262
3092
  /**
2263
3093
  * Load both system and user prompts from cache and replace variables
2264
- * @param promptName - Name of the prompt folder
3094
+ * @param promptName - Name of the prompt
2265
3095
  * @param variables - Variables to replace in the templates
2266
3096
  * @returns Object containing both system and user prompts
2267
3097
  */
2268
3098
  async loadPrompts(promptName, variables) {
2269
3099
  if (!this.isInitialized) {
2270
- logger.warn("PromptLoader not initialized, loading prompts on-demand (not recommended)");
3100
+ logger.warn("PromptLoader not initialized, initializing now...");
2271
3101
  await this.initialize();
2272
3102
  }
2273
3103
  const template = this.promptCache.get(promptName);
@@ -2295,6 +3125,7 @@ var PromptLoader = class {
2295
3125
  this.promptsDir = dir;
2296
3126
  this.isInitialized = false;
2297
3127
  this.promptCache.clear();
3128
+ logger.debug(`Prompts directory changed to: ${dir}`);
2298
3129
  }
2299
3130
  /**
2300
3131
  * Get current prompts directory
@@ -2586,6 +3417,42 @@ var LLM = class {
2586
3417
  }
2587
3418
  };
2588
3419
 
3420
+ // src/userResponse/knowledge-base.ts
3421
+ var getKnowledgeBase = async ({
3422
+ prompt,
3423
+ collections,
3424
+ topK = 1
3425
+ }) => {
3426
+ try {
3427
+ if (!collections || !collections["knowledge-base"] || !collections["knowledge-base"]["query"]) {
3428
+ logger.info("[KnowledgeBase] knowledge-base.query collection not registered, skipping");
3429
+ return "";
3430
+ }
3431
+ logger.info(`[KnowledgeBase] Querying knowledge base for: "${prompt.substring(0, 50)}..."`);
3432
+ const result = await collections["knowledge-base"]["query"]({
3433
+ prompt,
3434
+ topK
3435
+ });
3436
+ if (!result || !result.content) {
3437
+ logger.error("[KnowledgeBase] No knowledge base results returned");
3438
+ return "";
3439
+ }
3440
+ logger.info(`[KnowledgeBase] Retrieved knowledge base context (${result.content.length} chars)`);
3441
+ if (result.metadata?.sources && result.metadata.sources.length > 0) {
3442
+ logger.debug(`[KnowledgeBase] Sources: ${result.metadata.sources.map((s) => s.title).join(", ")}`);
3443
+ }
3444
+ return result.content;
3445
+ } catch (error) {
3446
+ const errorMsg = error instanceof Error ? error.message : String(error);
3447
+ logger.warn(`[KnowledgeBase] Error querying knowledge base: ${errorMsg}`);
3448
+ return "";
3449
+ }
3450
+ };
3451
+ var KB = {
3452
+ getKnowledgeBase
3453
+ };
3454
+ var knowledge_base_default = KB;
3455
+
2589
3456
  // src/userResponse/base-llm.ts
2590
3457
  var BaseLLM = class {
2591
3458
  constructor(config) {
@@ -3125,13 +3992,14 @@ var BaseLLM = class {
3125
3992
  }
3126
3993
  }
3127
3994
  /**
3128
- * Match components from text response suggestions
3995
+ * Match components from text response suggestions and generate follow-up questions
3129
3996
  * Takes a text response with component suggestions (c1:type format) and matches with available components
3997
+ * Also generates intelligent follow-up questions (actions) based on the analysis
3130
3998
  * @param textResponse - The text response containing component suggestions
3131
3999
  * @param components - List of available components
3132
4000
  * @param apiKey - Optional API key
3133
4001
  * @param logCollector - Optional log collector
3134
- * @returns Object containing matched components, selected layout, and reasoning
4002
+ * @returns Object containing matched components, selected layout, reasoning, and follow-up actions
3135
4003
  */
3136
4004
  async matchComponentsFromTextResponse(textResponse, components, apiKey, logCollector) {
3137
4005
  try {
@@ -3173,7 +4041,6 @@ var BaseLLM = class {
3173
4041
  // Don't parse as JSON yet, get raw response
3174
4042
  );
3175
4043
  logger.debug(`[${this.getProviderName()}] Raw component matching response length: ${rawResponse?.length || 0}`);
3176
- logger.file(`[${this.getProviderName()}] Component matching raw response:`, rawResponse);
3177
4044
  let result;
3178
4045
  try {
3179
4046
  let cleanedResponse = rawResponse || "";
@@ -3216,7 +4083,8 @@ var BaseLLM = class {
3216
4083
  selectedLayout: "MultiComponentContainer",
3217
4084
  selectedLayoutId: "",
3218
4085
  selectedLayoutComponent: null,
3219
- layoutReasoning: "No layout selected"
4086
+ layoutReasoning: "No layout selected",
4087
+ actions: []
3220
4088
  };
3221
4089
  }
3222
4090
  }
@@ -3224,6 +4092,8 @@ var BaseLLM = class {
3224
4092
  const selectedLayout = result.selectedLayout || "MultiComponentContainer";
3225
4093
  const selectedLayoutId = result.selectedLayoutId || "";
3226
4094
  const layoutReasoning = result.layoutReasoning || "No layout reasoning provided";
4095
+ const rawActions = result.actions || [];
4096
+ const actions = convertQuestionsToActions(rawActions);
3227
4097
  let selectedLayoutComponent = null;
3228
4098
  if (selectedLayoutId) {
3229
4099
  selectedLayoutComponent = components.find((c) => c.id === selectedLayoutId) || null;
@@ -3234,6 +4104,7 @@ var BaseLLM = class {
3234
4104
  logger.info(`[${this.getProviderName()}] Matched ${matchedComponents.length} components from text response`);
3235
4105
  logger.info(`[${this.getProviderName()}] Selected layout: ${selectedLayout} (ID: ${selectedLayoutId})`);
3236
4106
  logger.info(`[${this.getProviderName()}] Layout reasoning: ${layoutReasoning}`);
4107
+ logger.info(`[${this.getProviderName()}] Generated ${actions.length} follow-up actions`);
3237
4108
  if (matchedComponents.length > 0) {
3238
4109
  logCollector?.info(`Matched ${matchedComponents.length} components for visualization using ${selectedLayout}`);
3239
4110
  logCollector?.info(`Layout reasoning: ${layoutReasoning}`);
@@ -3248,6 +4119,12 @@ var BaseLLM = class {
3248
4119
  }
3249
4120
  });
3250
4121
  }
4122
+ if (actions.length > 0) {
4123
+ logCollector?.info(`Generated ${actions.length} follow-up questions`);
4124
+ actions.forEach((action, idx) => {
4125
+ logCollector?.info(` ${idx + 1}. ${action.name}`);
4126
+ });
4127
+ }
3251
4128
  const finalComponents = matchedComponents.map((mc) => {
3252
4129
  const originalComponent = components.find((c) => c.id === mc.componentId);
3253
4130
  if (!originalComponent) {
@@ -3267,7 +4144,8 @@ var BaseLLM = class {
3267
4144
  selectedLayout,
3268
4145
  selectedLayoutId,
3269
4146
  selectedLayoutComponent,
3270
- layoutReasoning
4147
+ layoutReasoning,
4148
+ actions
3271
4149
  };
3272
4150
  } catch (error) {
3273
4151
  const errorMsg = error instanceof Error ? error.message : String(error);
@@ -3279,7 +4157,8 @@ var BaseLLM = class {
3279
4157
  selectedLayout: "MultiComponentContainer",
3280
4158
  selectedLayoutId: "",
3281
4159
  selectedLayoutComponent: null,
3282
- layoutReasoning: "Error occurred during component matching"
4160
+ layoutReasoning: "Error occurred during component matching",
4161
+ actions: []
3283
4162
  };
3284
4163
  }
3285
4164
  }
@@ -3298,10 +4177,17 @@ var BaseLLM = class {
3298
4177
  logger.debug(`[${this.getProviderName()}] User prompt: "${userPrompt.substring(0, 50)}..."`);
3299
4178
  try {
3300
4179
  const schemaDoc = schema.generateSchemaDocumentation();
4180
+ const knowledgeBaseContext = await knowledge_base_default.getKnowledgeBase({
4181
+ prompt: userPrompt,
4182
+ collections,
4183
+ topK: 1
4184
+ });
4185
+ logger.file("\n=============================\nknowledge base context:", knowledgeBaseContext);
3301
4186
  const prompts = await promptLoader.loadPrompts("text-response", {
3302
4187
  USER_PROMPT: userPrompt,
3303
4188
  CONVERSATION_HISTORY: conversationHistory || "No previous conversation",
3304
- SCHEMA_DOC: schemaDoc
4189
+ SCHEMA_DOC: schemaDoc,
4190
+ KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext || "No additional knowledge base context available."
3305
4191
  });
3306
4192
  logger.file("\n=============================\nsystem prompt:", prompts.system);
3307
4193
  logger.file("\n=============================\nuser prompt:", prompts.user);
@@ -3502,6 +4388,7 @@ ${errorMsg}
3502
4388
  text: textResponse,
3503
4389
  // Include the streamed text showing all attempts
3504
4390
  matchedComponents: [],
4391
+ actions: [],
3505
4392
  method: `${this.getProviderName()}-text-response-max-attempts`
3506
4393
  }
3507
4394
  };
@@ -3517,6 +4404,7 @@ ${errorMsg}
3517
4404
  let matchedComponents = [];
3518
4405
  let selectedLayoutComponent = null;
3519
4406
  let layoutReasoning = "No layout selected";
4407
+ let actions = [];
3520
4408
  if (components && components.length > 0) {
3521
4409
  logger.info(`[${this.getProviderName()}] Matching components from text response...`);
3522
4410
  const matchResult = await this.matchComponentsFromTextResponse(
@@ -3528,6 +4416,7 @@ ${errorMsg}
3528
4416
  matchedComponents = matchResult.components;
3529
4417
  selectedLayoutComponent = matchResult.selectedLayoutComponent;
3530
4418
  layoutReasoning = matchResult.layoutReasoning;
4419
+ actions = matchResult.actions;
3531
4420
  }
3532
4421
  let container_componet = null;
3533
4422
  if (matchedComponents.length > 0) {
@@ -3541,11 +4430,12 @@ ${errorMsg}
3541
4430
  config: {
3542
4431
  ...selectedLayoutComponent.props?.config || {},
3543
4432
  components: matchedComponents
3544
- }
4433
+ },
4434
+ actions
3545
4435
  }
3546
4436
  };
3547
- logger.info(`[${this.getProviderName()}] Created ${selectedLayoutComponent.name} (${selectedLayoutComponent.type}) container with ${matchedComponents.length} components`);
3548
- logCollector?.info(`Created ${selectedLayoutComponent.name} with ${matchedComponents.length} components: ${layoutReasoning}`);
4437
+ logger.info(`[${this.getProviderName()}] Created ${selectedLayoutComponent.name} (${selectedLayoutComponent.type}) container with ${matchedComponents.length} components and ${actions.length} actions`);
4438
+ logCollector?.info(`Created ${selectedLayoutComponent.name} with ${matchedComponents.length} components and ${actions.length} actions: ${layoutReasoning}`);
3549
4439
  } else {
3550
4440
  container_componet = {
3551
4441
  id: `multi_container_${Date.now()}`,
@@ -3557,11 +4447,12 @@ ${errorMsg}
3557
4447
  props: {
3558
4448
  config: {
3559
4449
  components: matchedComponents
3560
- }
4450
+ },
4451
+ actions
3561
4452
  }
3562
4453
  };
3563
- logger.info(`[${this.getProviderName()}] Created fallback MultiComponentContainer with ${matchedComponents.length} components`);
3564
- logCollector?.info(`Created MultiComponentContainer with ${matchedComponents.length} components: ${layoutReasoning}`);
4454
+ logger.info(`[${this.getProviderName()}] Created fallback MultiComponentContainer with ${matchedComponents.length} components and ${actions.length} actions`);
4455
+ logCollector?.info(`Created MultiComponentContainer with ${matchedComponents.length} components and ${actions.length} actions: ${layoutReasoning}`);
3565
4456
  }
3566
4457
  }
3567
4458
  return {
@@ -3571,6 +4462,7 @@ ${errorMsg}
3571
4462
  matchedComponents,
3572
4463
  component: container_componet,
3573
4464
  layoutReasoning,
4465
+ actions,
3574
4466
  method: `${this.getProviderName()}-text-response`
3575
4467
  },
3576
4468
  errors: []
@@ -3586,6 +4478,7 @@ ${errorMsg}
3586
4478
  data: {
3587
4479
  text: "I apologize, but I encountered an error while processing your question. Please try rephrasing or ask something else.",
3588
4480
  matchedComponents: [],
4481
+ actions: [],
3589
4482
  method: `${this.getProviderName()}-text-response-error`
3590
4483
  }
3591
4484
  };
@@ -4336,6 +5229,7 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
4336
5229
  }
4337
5230
  let component = null;
4338
5231
  let textResponse = null;
5232
+ let actions = [];
4339
5233
  if (userResponse.data) {
4340
5234
  if (typeof userResponse.data === "object") {
4341
5235
  if ("component" in userResponse.data) {
@@ -4344,6 +5238,9 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
4344
5238
  if ("textResponse" in userResponse.data) {
4345
5239
  textResponse = userResponse.data.text;
4346
5240
  }
5241
+ if ("actions" in userResponse.data) {
5242
+ actions = userResponse.data.actions || [];
5243
+ }
4347
5244
  }
4348
5245
  }
4349
5246
  const finalTextResponse = responseMode === "text" && accumulatedStreamResponse ? accumulatedStreamResponse : textResponse;
@@ -4354,11 +5251,15 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
4354
5251
  component,
4355
5252
  // generatedComponentMetadata: full component object (ComponentSchema)
4356
5253
  [],
4357
- // actions: empty initially
5254
+ // actions: empty initially, will be set below
4358
5255
  uiBlockId,
4359
5256
  finalTextResponse
4360
5257
  // textResponse: FULL streaming response including all intermediate messages
4361
5258
  );
5259
+ if (actions.length > 0) {
5260
+ uiBlock.setActions(actions);
5261
+ logger.info(`Stored ${actions.length} actions in UIBlock: ${uiBlockId}`);
5262
+ }
4362
5263
  thread.addUIBlock(uiBlock);
4363
5264
  logger.info(`Created UIBlock: ${uiBlockId} in Thread: ${threadId}`);
4364
5265
  return {
@@ -4596,7 +5497,6 @@ async function handleActionsRequest(data, sendMessage, anthropicApiKey, groqApiK
4596
5497
  const uiBlockId = SA_RUNTIME.uiBlockId;
4597
5498
  const threadId = SA_RUNTIME.threadId;
4598
5499
  logger.debug(`[ACTIONS_REQ ${id}] SA_RUNTIME validated - threadId: ${threadId}, uiBlockId: ${uiBlockId}`);
4599
- logger.debug(`[ACTIONS_REQ ${id}] Retrieving thread: ${threadId}`);
4600
5500
  const threadManager = ThreadManager.getInstance();
4601
5501
  const thread = threadManager.getThread(threadId);
4602
5502
  if (!thread) {
@@ -6297,6 +7197,7 @@ var SuperatomSDK = class {
6297
7197
  }
6298
7198
  /**
6299
7199
  * Initialize PromptLoader and load prompts into memory
7200
+ * Tries to load from file system first, then falls back to hardcoded prompts
6300
7201
  */
6301
7202
  async initializePromptLoader(promptsDir) {
6302
7203
  try {