@superatomai/sdk-node 0.0.7 → 0.0.9
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.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +985 -154
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +985 -154
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -2187,17 +2187,875 @@ var schema = new Schema();
|
|
|
2187
2187
|
// src/userResponse/prompt-loader.ts
|
|
2188
2188
|
import fs4 from "fs";
|
|
2189
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
|
+
<DashboardComponents>
|
|
2678
|
+
**Dashboard Components:**
|
|
2679
|
+
Format: \`{number}.{component_type} : {clear reasoning}\`
|
|
2680
|
+
|
|
2681
|
+
|
|
2682
|
+
**Rules for component suggestions:**
|
|
2683
|
+
1. Analyze the query results structure and data type
|
|
2684
|
+
2. Suggest components that would best visualize the data
|
|
2685
|
+
3. Each component suggestion must be on a new line
|
|
2686
|
+
</DashboardComponents>
|
|
2687
|
+
|
|
2688
|
+
IMPORTANT: Always wrap component suggestions with <DashboardComponents> tags and include at least one component suggestion when data is returned.
|
|
2689
|
+
|
|
2690
|
+
## Output Format
|
|
2691
|
+
|
|
2692
|
+
Respond with plain text that includes:
|
|
2693
|
+
|
|
2694
|
+
1. **Query Analysis** (if applicable): Brief explanation of what data was fetched
|
|
2695
|
+
2. **Results Summary**: Present the data in a clear, readable format
|
|
2696
|
+
3. **Dashboard Components**: List suggested components wrapped in <DashboardComponents> tags
|
|
2697
|
+
|
|
2698
|
+
|
|
2699
|
+
**CRITICAL:**
|
|
2700
|
+
- Return ONLY plain text (no JSON, no markdown code blocks)
|
|
2701
|
+
|
|
2702
|
+
|
|
2703
|
+
You have access to a database and can execute SQL queries to answer data-related questions.
|
|
2704
|
+
## Database Schema
|
|
2705
|
+
{{SCHEMA_DOC}}
|
|
2706
|
+
|
|
2707
|
+
**Database Type: PostgreSQL**
|
|
2708
|
+
|
|
2709
|
+
**CRITICAL PostgreSQL Query Rules:**
|
|
2710
|
+
|
|
2711
|
+
1. **NO AGGREGATE FUNCTIONS IN WHERE CLAUSE** - This is a fundamental SQL error
|
|
2712
|
+
\u274C WRONG: \`WHERE COUNT(orders) > 0\`
|
|
2713
|
+
\u274C WRONG: \`WHERE SUM(price) > 100\`
|
|
2714
|
+
\u274C WRONG: \`WHERE AVG(rating) > 4.5\`
|
|
2715
|
+
|
|
2716
|
+
\u2705 CORRECT: Use HAVING (with GROUP BY), EXISTS, or subquery
|
|
2717
|
+
|
|
2718
|
+
2. **WHERE vs HAVING**
|
|
2719
|
+
- WHERE filters rows BEFORE grouping (cannot use aggregates)
|
|
2720
|
+
- HAVING filters groups AFTER grouping (can use aggregates)
|
|
2721
|
+
- If using HAVING, you MUST have GROUP BY
|
|
2722
|
+
|
|
2723
|
+
3. **NO NESTED AGGREGATE FUNCTIONS** - PostgreSQL does NOT allow aggregates inside aggregates
|
|
2724
|
+
\u274C WRONG: \`AVG(ROUND(AVG(column), 2))\` or \`SELECT AVG(SUM(price)) FROM ...\`
|
|
2725
|
+
\u2705 CORRECT: \`ROUND(AVG(column), 2)\`
|
|
2726
|
+
|
|
2727
|
+
4. **GROUP BY Requirements**
|
|
2728
|
+
- ALL non-aggregated columns in SELECT must be in GROUP BY
|
|
2729
|
+
- If you SELECT a column and don't aggregate it, add it to GROUP BY
|
|
2730
|
+
|
|
2731
|
+
5. **LIMIT Clause**
|
|
2732
|
+
- ALWAYS include LIMIT (max 32 rows)
|
|
2733
|
+
- For scalar subqueries in WHERE/HAVING, add LIMIT 1
|
|
2734
|
+
|
|
2735
|
+
|
|
2736
|
+
## Knowledge Base Context
|
|
2737
|
+
|
|
2738
|
+
The following relevant information has been retrieved from the knowledge base for this question:
|
|
2739
|
+
|
|
2740
|
+
{{KNOWLEDGE_BASE_CONTEXT}}
|
|
2741
|
+
|
|
2742
|
+
Use this knowledge base information to:
|
|
2743
|
+
- Provide more accurate and informed responses
|
|
2744
|
+
- Reference specific facts, concepts, or domain knowledge
|
|
2745
|
+
- Enhance your understanding of the user's question
|
|
2746
|
+
- Give context-aware recommendations
|
|
2747
|
+
|
|
2748
|
+
**Note:** If there is previous conversation history, use it to understand context and provide coherent responses:
|
|
2749
|
+
- Reference previous questions and answers when relevant
|
|
2750
|
+
- Maintain consistency with earlier responses
|
|
2751
|
+
- Use the history to resolve ambiguous references like "that", "it", "them", "the previous data"
|
|
2752
|
+
|
|
2753
|
+
## Previous Conversation
|
|
2754
|
+
{{CONVERSATION_HISTORY}}
|
|
2755
|
+
|
|
2756
|
+
|
|
2757
|
+
`,
|
|
2758
|
+
user: `{{USER_PROMPT}}
|
|
2759
|
+
|
|
2760
|
+
`
|
|
2761
|
+
},
|
|
2762
|
+
"match-text-components": {
|
|
2763
|
+
system: `You are a component matching expert that creates beautiful, well-structured dashboard visualizations from analysis results.
|
|
2764
|
+
|
|
2765
|
+
## Your Task
|
|
2766
|
+
|
|
2767
|
+
You will receive a text response containing:
|
|
2768
|
+
1. Query execution results (with "\u2705 Query executed successfully!" markers)
|
|
2769
|
+
2. Data analysis and insights
|
|
2770
|
+
3. **Dashboard Components:** suggestions (1:component_type : reasoning format)
|
|
2771
|
+
|
|
2772
|
+
Your job is to:
|
|
2773
|
+
1. **Parse the component suggestions** from the text response (format: 1:component_type : reasoning)
|
|
2774
|
+
2. **Match each suggestion with an actual component** from the available list
|
|
2775
|
+
3. **Generate proper props** for each matched component to **visualize the analysis results** that were already fetched
|
|
2776
|
+
4. **SELECT the best dashboard layout component** that can accommodate all the matched components
|
|
2777
|
+
5. **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
|
+
**APPROACH**: First match all the components suggested in the text response, THEN find the layout that best fits those components.
|
|
2782
|
+
|
|
2783
|
+
## Available Components
|
|
2784
|
+
|
|
2785
|
+
{{AVAILABLE_COMPONENTS}}
|
|
2786
|
+
|
|
2787
|
+
## Component Matching Rules (STEP 1)
|
|
2788
|
+
For each component suggestion (c1, c2, c3, etc.) from the text response:
|
|
2789
|
+
|
|
2790
|
+
1. **Match by type**: Find components whose \`type\` matches the suggested component type
|
|
2791
|
+
2. **Refine by relevance**: If multiple components match, choose based on:
|
|
2792
|
+
- Description and keywords matching the use case
|
|
2793
|
+
- Best fit for the data being visualized
|
|
2794
|
+
3. **Fallback**: If no exact type match, find the closest alternative
|
|
2795
|
+
|
|
2796
|
+
## Layout Selection Logic (STEP 2 - After Matching Components)
|
|
2797
|
+
|
|
2798
|
+
**After you have matched all components**, select the best dashboard layout:
|
|
2799
|
+
|
|
2800
|
+
1. **Find layout components** by looking for components with \`type: "DashboardLayout"\` in the available components list
|
|
2801
|
+
2. **Read each layout's description** to understand:
|
|
2802
|
+
- What structure it provides
|
|
2803
|
+
- When it's best used (e.g., comprehensive analysis vs focused analysis)
|
|
2804
|
+
- The number and types of components it can accommodate
|
|
2805
|
+
3. **Select the best layout** based on:
|
|
2806
|
+
- Which layout can best display ALL the matched components
|
|
2807
|
+
- The layout's capacity (how many components it supports)
|
|
2808
|
+
- The types of matched components (KPI, charts, tables, etc.)
|
|
2809
|
+
- The user question and data complexity
|
|
2810
|
+
4. **If no specific layout fits**, fall back to "MultiComponentContainer" as the default layout
|
|
2811
|
+
|
|
2812
|
+
**IMPORTANT:** The layout should be chosen to FIT the matched components, not the other way around. Don't force components to fit a layout - find a layout that accommodates your components.
|
|
2813
|
+
|
|
2814
|
+
## Props Generation Rules
|
|
2815
|
+
|
|
2816
|
+
For each matched component, generate complete props:
|
|
2817
|
+
|
|
2818
|
+
### 1. Query - When to Reuse vs Generate
|
|
2819
|
+
|
|
2820
|
+
**Option A: REUSE the successful query** (preferred when possible)
|
|
2821
|
+
- Look for "\u2705 Query executed successfully!" in the text response
|
|
2822
|
+
- Extract the exact SQL query that worked
|
|
2823
|
+
- Use this SAME query for components that visualize the same data differently
|
|
2824
|
+
|
|
2825
|
+
**Option B: GENERATE a new query** (when necessary)
|
|
2826
|
+
- Only generate new queries when you need DIFFERENT data
|
|
2827
|
+
- Use the database schema below to write valid SQL
|
|
2828
|
+
|
|
2829
|
+
|
|
2830
|
+
**Decision Logic:**
|
|
2831
|
+
- Different aggregation needed \u2192 Generate new query \u2705
|
|
2832
|
+
- Same data, different view \u2192 Reuse query \u2705
|
|
2833
|
+
|
|
2834
|
+
**Database Schema:**
|
|
2835
|
+
{{SCHEMA_DOC}}
|
|
2836
|
+
|
|
2837
|
+
**Database Type: PostgreSQL**
|
|
2838
|
+
|
|
2839
|
+
**CRITICAL PostgreSQL Query Rules**
|
|
2840
|
+
|
|
2841
|
+
1. **NO AGGREGATE FUNCTIONS IN WHERE CLAUSE** - This is a fundamental SQL error
|
|
2842
|
+
\u274C WRONG: \`WHERE COUNT(orders) > 0\`
|
|
2843
|
+
\u274C WRONG: \`WHERE SUM(price) > 100\`
|
|
2844
|
+
\u274C WRONG: \`WHERE AVG(rating) > 4.5\`
|
|
2845
|
+
|
|
2846
|
+
\u2705 CORRECT: Use HAVING (with GROUP BY), EXISTS, or subquery
|
|
2847
|
+
|
|
2848
|
+
2. **NO NESTED AGGREGATE FUNCTIONS** - PostgreSQL does NOT allow aggregates inside aggregates
|
|
2849
|
+
\u274C WRONG: \`AVG(ROUND(AVG(column), 2))\`
|
|
2850
|
+
\u2705 CORRECT: \`ROUND(AVG(column), 2)\`
|
|
2851
|
+
|
|
2852
|
+
3. **Aggregate Functions Can Only Appear Once Per Level**
|
|
2853
|
+
\u274C WRONG: \`SELECT AVG(SUM(price)) FROM ...\` (nested)
|
|
2854
|
+
\u2705 CORRECT: Use subquery: \`SELECT AVG(total) FROM (SELECT SUM(price) as total FROM ... GROUP BY ...) subq\`
|
|
2855
|
+
|
|
2856
|
+
4. **WHERE vs HAVING**
|
|
2857
|
+
- WHERE filters rows BEFORE grouping (cannot use aggregates)
|
|
2858
|
+
- HAVING filters groups AFTER grouping (can use aggregates)
|
|
2859
|
+
- Use WHERE for column comparisons: \`WHERE price > 100\`
|
|
2860
|
+
- Use HAVING for aggregate comparisons: \`HAVING COUNT(*) > 5\`
|
|
2861
|
+
|
|
2862
|
+
5. **GROUP BY Requirements**
|
|
2863
|
+
- ALL non-aggregated columns in SELECT must be in GROUP BY
|
|
2864
|
+
- Use proper column references (table.column or aliases)
|
|
2865
|
+
- If using HAVING, you MUST have GROUP BY
|
|
2866
|
+
|
|
2867
|
+
6. **LIMIT Clause**
|
|
2868
|
+
- ALWAYS include LIMIT clause (max 32 rows for dashboard queries)
|
|
2869
|
+
- For scalar subqueries in WHERE/HAVING, add LIMIT 1
|
|
2870
|
+
|
|
2871
|
+
7. **Scalar Subqueries**
|
|
2872
|
+
- Subqueries used with =, <, >, etc. must return single value
|
|
2873
|
+
- Always add LIMIT 1 to scalar subqueries
|
|
2874
|
+
|
|
2875
|
+
**Query Generation Guidelines** (when creating new queries):
|
|
2876
|
+
- Use correct table and column names from the schema above
|
|
2877
|
+
- ALWAYS include LIMIT clause (max 32 rows)
|
|
2878
|
+
|
|
2879
|
+
### 2. Title
|
|
2880
|
+
- Create a clear, descriptive title
|
|
2881
|
+
- Should explain what the component shows
|
|
2882
|
+
- Use context from the original question
|
|
2883
|
+
|
|
2884
|
+
### 3. Description
|
|
2885
|
+
- Brief explanation of what this component displays
|
|
2886
|
+
- Why it's useful for this data
|
|
2887
|
+
|
|
2888
|
+
### 4. Config
|
|
2889
|
+
- **CRITICAL**: Look at the component's "Props Structure" to see what config fields it expects
|
|
2890
|
+
- Map query result columns to the appropriate config fields
|
|
2891
|
+
- Keep other existing config properties that don't need to change
|
|
2892
|
+
- Ensure config field values match actual column names from the query
|
|
2893
|
+
|
|
2894
|
+
**Special Rules for Bar Charts**
|
|
2895
|
+
- \`xAxisKey\` = ALWAYS the category/label column (text field )
|
|
2896
|
+
- \`yAxisKey\` = ALWAYS the numeric value column (number field )
|
|
2897
|
+
- \`orientation\` = "vertical" or "horizontal" (controls visual direction only)
|
|
2898
|
+
- **DO NOT swap xAxisKey/yAxisKey based on orientation** - they always represent category and value respectively
|
|
2899
|
+
|
|
2900
|
+
## Follow-Up Questions (Actions) Generation
|
|
2901
|
+
|
|
2902
|
+
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:
|
|
2903
|
+
|
|
2904
|
+
1. **Build upon the data analysis** shown in the text response and components
|
|
2905
|
+
2. **Explore natural next steps** in the data exploration journey
|
|
2906
|
+
3. **Be progressively more detailed or specific** - go deeper into the analysis
|
|
2907
|
+
4. **Consider the insights revealed** - suggest questions that help users understand implications
|
|
2908
|
+
5. **Be phrased naturally** as if a real user would ask them
|
|
2909
|
+
6. **Vary in scope** - include both broad trends and specific details
|
|
2910
|
+
7. **Avoid redundancy** - don't ask questions already answered in the text response
|
|
2911
|
+
|
|
2912
|
+
|
|
2913
|
+
## Output Format
|
|
2914
|
+
|
|
2915
|
+
You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
|
|
2916
|
+
|
|
2917
|
+
**IMPORTANT JSON FORMATTING RULES:**
|
|
2918
|
+
- Put SQL queries on a SINGLE LINE (no newlines in the query string)
|
|
2919
|
+
- Escape all quotes in SQL properly (use \\" for quotes inside strings)
|
|
2920
|
+
- Remove any newlines, tabs, or special characters from SQL
|
|
2921
|
+
- Do NOT use markdown code blocks (no \`\`\`)
|
|
2922
|
+
- Return ONLY the JSON object, nothing else
|
|
2923
|
+
|
|
2924
|
+
\`\`\`json
|
|
2925
|
+
{
|
|
2926
|
+
"selectedLayoutId": "id_of_the_selected_layout_component",
|
|
2927
|
+
"layoutReasoning": "Why this layout was selected based on its description and the analysis needs",
|
|
2928
|
+
"matchedComponents": [
|
|
2929
|
+
{
|
|
2930
|
+
"componentId": "id_from_available_list",
|
|
2931
|
+
"componentName": "name_of_component",
|
|
2932
|
+
"componentType": "type_of_component",
|
|
2933
|
+
"reasoning": "Why this component was selected for this suggestion",
|
|
2934
|
+
"originalSuggestion": "c1:table : original reasoning from text",
|
|
2935
|
+
"props": {
|
|
2936
|
+
"query": "SQL query for this component",
|
|
2937
|
+
"title": "Component title",
|
|
2938
|
+
"description": "Component description",
|
|
2939
|
+
"config": {
|
|
2940
|
+
"field1": "value1",
|
|
2941
|
+
"field2": "value2"
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
],
|
|
2946
|
+
"actions": [
|
|
2947
|
+
"Follow-up question 1?",
|
|
2948
|
+
"Follow-up question 2?",
|
|
2949
|
+
"Follow-up question 3?",
|
|
2950
|
+
"Follow-up question 4?",
|
|
2951
|
+
"Follow-up question 5?"
|
|
2952
|
+
]
|
|
2953
|
+
}
|
|
2954
|
+
\`\`\`
|
|
2955
|
+
|
|
2956
|
+
**CRITICAL:**
|
|
2957
|
+
- \`matchedComponents\` MUST include ALL components suggested in the text response (match them first!)
|
|
2958
|
+
- \`selectedLayoutId\` MUST be the ID of the selected layout component (must have type "DashboardLayout")
|
|
2959
|
+
- \`layoutReasoning\` MUST explain:
|
|
2960
|
+
- Why you chose this specific layout component
|
|
2961
|
+
- How many components you matched (e.g., "Matched 3 components: 1 KPI, 1 chart, 1 table")
|
|
2962
|
+
- Why this layout is the best fit for displaying these specific matched components
|
|
2963
|
+
- What makes this layout appropriate for the component types and count
|
|
2964
|
+
- The layout selection happens AFTER component matching - don't force components to fit a pre-selected layout
|
|
2965
|
+
- \`actions\` MUST be an array of 4-5 intelligent follow-up questions based on the analysis
|
|
2966
|
+
- Return ONLY valid JSON (no markdown code blocks, no text before/after)
|
|
2967
|
+
- Generate complete props for each component
|
|
2968
|
+
|
|
2969
|
+
|
|
2970
|
+
`,
|
|
2971
|
+
user: `## Text Response
|
|
2972
|
+
|
|
2973
|
+
{{TEXT_RESPONSE}}
|
|
2974
|
+
|
|
2975
|
+
---
|
|
2976
|
+
|
|
2977
|
+
Match the component suggestions from the text response above with available components, and generate proper props for each matched component.
|
|
2978
|
+
`
|
|
2979
|
+
},
|
|
2980
|
+
"actions": {
|
|
2981
|
+
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.
|
|
2982
|
+
|
|
2983
|
+
## Previous Conversation
|
|
2984
|
+
{{CONVERSATION_HISTORY}}
|
|
2985
|
+
|
|
2986
|
+
**Context Instructions:**
|
|
2987
|
+
- Review the entire conversation history to understand the user's journey and interests
|
|
2988
|
+
- Suggest questions that naturally progress from previous explorations
|
|
2989
|
+
- Avoid suggesting questions about topics already covered in the conversation
|
|
2990
|
+
- Build upon insights from previous components to suggest deeper analysis
|
|
2991
|
+
- Consider what the user might want to explore next based on their question pattern`,
|
|
2992
|
+
user: `Given the following context:
|
|
2993
|
+
|
|
2994
|
+
Latest User Question: {{ORIGINAL_USER_PROMPT}}
|
|
2995
|
+
|
|
2996
|
+
Current Component:
|
|
2997
|
+
{{COMPONENT_INFO}}
|
|
2998
|
+
|
|
2999
|
+
{{COMPONENT_DATA}}
|
|
3000
|
+
|
|
3001
|
+
Generate a JSON array of 4-5 intelligent follow-up questions that the user might naturally ask next. These questions should:
|
|
3002
|
+
1. Build upon the conversation history and insights shown in the current component
|
|
3003
|
+
2. NOT repeat questions or topics already covered in the conversation history
|
|
3004
|
+
3. Explore natural next steps in the data exploration journey
|
|
3005
|
+
4. Be progressively more detailed or specific
|
|
3006
|
+
5. Consider the user's apparent interests based on their question pattern
|
|
3007
|
+
6. Be phrased naturally as if a real user would ask them
|
|
3008
|
+
|
|
3009
|
+
Format your response as a JSON object with this structure:
|
|
3010
|
+
{
|
|
3011
|
+
"nextQuestions": [
|
|
3012
|
+
"Question 1?",
|
|
3013
|
+
"Question 2?",
|
|
3014
|
+
...
|
|
3015
|
+
]
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
Return ONLY valid JSON.`
|
|
3019
|
+
}
|
|
3020
|
+
};
|
|
3021
|
+
|
|
3022
|
+
// src/userResponse/prompt-loader.ts
|
|
2190
3023
|
var PromptLoader = class {
|
|
2191
3024
|
constructor(config) {
|
|
2192
3025
|
this.promptCache = /* @__PURE__ */ new Map();
|
|
2193
3026
|
this.isInitialized = false;
|
|
2194
|
-
logger.debug("Initializing PromptLoader..."
|
|
3027
|
+
logger.debug("Initializing PromptLoader...");
|
|
2195
3028
|
this.promptsDir = config?.promptsDir || path3.join(process.cwd(), ".prompts");
|
|
2196
|
-
|
|
3029
|
+
logger.debug(`Prompts directory set to: ${this.promptsDir}`);
|
|
3030
|
+
}
|
|
3031
|
+
/**
|
|
3032
|
+
* Load a prompt template from file system OR fallback to hardcoded prompts
|
|
3033
|
+
* @param promptName - Name of the prompt folder
|
|
3034
|
+
* @returns Template with system and user prompts
|
|
3035
|
+
*/
|
|
3036
|
+
loadPromptTemplate(promptName) {
|
|
3037
|
+
try {
|
|
3038
|
+
const systemPath = path3.join(this.promptsDir, promptName, "system.md");
|
|
3039
|
+
const userPath = path3.join(this.promptsDir, promptName, "user.md");
|
|
3040
|
+
if (fs4.existsSync(systemPath) && fs4.existsSync(userPath)) {
|
|
3041
|
+
const system = fs4.readFileSync(systemPath, "utf-8");
|
|
3042
|
+
const user = fs4.readFileSync(userPath, "utf-8");
|
|
3043
|
+
logger.info(`\u2713 Loaded prompt '${promptName}' from file system: ${this.promptsDir}`);
|
|
3044
|
+
return { system, user };
|
|
3045
|
+
}
|
|
3046
|
+
} catch (error) {
|
|
3047
|
+
logger.error(`Could not load '${promptName}' from file system, trying fallback...`);
|
|
3048
|
+
}
|
|
3049
|
+
const hardcodedPrompt = PROMPTS[promptName];
|
|
3050
|
+
if (hardcodedPrompt) {
|
|
3051
|
+
logger.info(`\u2713 Loaded prompt '${promptName}' from hardcoded fallback`);
|
|
3052
|
+
return hardcodedPrompt;
|
|
3053
|
+
}
|
|
3054
|
+
throw new Error(`Prompt template '${promptName}' not found in either ${this.promptsDir} or hardcoded prompts. Available prompts: ${Object.keys(PROMPTS).join(", ")}`);
|
|
2197
3055
|
}
|
|
2198
3056
|
/**
|
|
2199
3057
|
* Initialize and cache all prompts into memory
|
|
2200
|
-
*
|
|
3058
|
+
* Tries file system first, then falls back to hardcoded prompts
|
|
2201
3059
|
*/
|
|
2202
3060
|
async initialize() {
|
|
2203
3061
|
if (this.isInitialized) {
|
|
@@ -2205,61 +3063,19 @@ var PromptLoader = class {
|
|
|
2205
3063
|
return;
|
|
2206
3064
|
}
|
|
2207
3065
|
logger.info("Loading prompts into memory...");
|
|
2208
|
-
const promptTypes =
|
|
2209
|
-
|
|
2210
|
-
"match-component",
|
|
2211
|
-
"modify-props",
|
|
2212
|
-
"single-component",
|
|
2213
|
-
"mutli-component",
|
|
2214
|
-
"actions",
|
|
2215
|
-
"container-metadata",
|
|
2216
|
-
"text-response",
|
|
2217
|
-
"match-text-components"
|
|
2218
|
-
];
|
|
2219
|
-
for (const promptType of promptTypes) {
|
|
3066
|
+
const promptTypes = Object.keys(PROMPTS);
|
|
3067
|
+
for (const promptName of promptTypes) {
|
|
2220
3068
|
try {
|
|
2221
|
-
const template =
|
|
2222
|
-
this.promptCache.set(
|
|
2223
|
-
logger.debug(`Cached prompt: ${promptType}`);
|
|
3069
|
+
const template = this.loadPromptTemplate(promptName);
|
|
3070
|
+
this.promptCache.set(promptName, template);
|
|
2224
3071
|
} catch (error) {
|
|
2225
|
-
logger.error(`Failed to load prompt '${
|
|
3072
|
+
logger.error(`Failed to load prompt '${promptName}':`, error);
|
|
2226
3073
|
throw error;
|
|
2227
3074
|
}
|
|
2228
3075
|
}
|
|
2229
3076
|
this.isInitialized = true;
|
|
2230
3077
|
logger.info(`Successfully loaded ${this.promptCache.size} prompt templates into memory`);
|
|
2231
3078
|
}
|
|
2232
|
-
/**
|
|
2233
|
-
* Load a prompt template from file system (tries custom dir first, then defaults to SDK dir)
|
|
2234
|
-
* @param promptName - Name of the prompt folder
|
|
2235
|
-
* @returns Template with system and user prompts
|
|
2236
|
-
*/
|
|
2237
|
-
async loadPromptTemplate(promptName) {
|
|
2238
|
-
const tryLoadFromDir = (dir) => {
|
|
2239
|
-
try {
|
|
2240
|
-
const systemPath = path3.join(dir, promptName, "system.md");
|
|
2241
|
-
const userPath = path3.join(dir, promptName, "user.md");
|
|
2242
|
-
if (fs4.existsSync(systemPath) && fs4.existsSync(userPath)) {
|
|
2243
|
-
const system = fs4.readFileSync(systemPath, "utf-8");
|
|
2244
|
-
const user = fs4.readFileSync(userPath, "utf-8");
|
|
2245
|
-
logger.debug(`Loaded prompt '${promptName}' from ${dir}`);
|
|
2246
|
-
return { system, user };
|
|
2247
|
-
}
|
|
2248
|
-
return null;
|
|
2249
|
-
} catch (error) {
|
|
2250
|
-
return null;
|
|
2251
|
-
}
|
|
2252
|
-
};
|
|
2253
|
-
let template = tryLoadFromDir(this.promptsDir);
|
|
2254
|
-
if (!template) {
|
|
2255
|
-
logger.warn(`Prompt '${promptName}' not found in ${this.promptsDir}, trying default location...`);
|
|
2256
|
-
template = tryLoadFromDir(this.defaultPromptsDir);
|
|
2257
|
-
}
|
|
2258
|
-
if (!template) {
|
|
2259
|
-
throw new Error(`Prompt template '${promptName}' not found in either ${this.promptsDir} or ${this.defaultPromptsDir}`);
|
|
2260
|
-
}
|
|
2261
|
-
return template;
|
|
2262
|
-
}
|
|
2263
3079
|
/**
|
|
2264
3080
|
* Replace variables in a template string using {{VARIABLE_NAME}} pattern
|
|
2265
3081
|
* @param template - Template string with placeholders
|
|
@@ -2277,13 +3093,13 @@ var PromptLoader = class {
|
|
|
2277
3093
|
}
|
|
2278
3094
|
/**
|
|
2279
3095
|
* Load both system and user prompts from cache and replace variables
|
|
2280
|
-
* @param promptName - Name of the prompt
|
|
3096
|
+
* @param promptName - Name of the prompt
|
|
2281
3097
|
* @param variables - Variables to replace in the templates
|
|
2282
3098
|
* @returns Object containing both system and user prompts
|
|
2283
3099
|
*/
|
|
2284
3100
|
async loadPrompts(promptName, variables) {
|
|
2285
3101
|
if (!this.isInitialized) {
|
|
2286
|
-
logger.warn("PromptLoader not initialized,
|
|
3102
|
+
logger.warn("PromptLoader not initialized, initializing now...");
|
|
2287
3103
|
await this.initialize();
|
|
2288
3104
|
}
|
|
2289
3105
|
const template = this.promptCache.get(promptName);
|
|
@@ -2311,6 +3127,7 @@ var PromptLoader = class {
|
|
|
2311
3127
|
this.promptsDir = dir;
|
|
2312
3128
|
this.isInitialized = false;
|
|
2313
3129
|
this.promptCache.clear();
|
|
3130
|
+
logger.debug(`Prompts directory changed to: ${dir}`);
|
|
2314
3131
|
}
|
|
2315
3132
|
/**
|
|
2316
3133
|
* Get current prompts directory
|
|
@@ -2340,6 +3157,7 @@ var promptLoader = new PromptLoader({
|
|
|
2340
3157
|
// src/llm.ts
|
|
2341
3158
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2342
3159
|
import Groq from "groq-sdk";
|
|
3160
|
+
import { jsonrepair } from "jsonrepair";
|
|
2343
3161
|
var LLM = class {
|
|
2344
3162
|
/* Get a complete text response from an LLM (Anthropic or Groq) */
|
|
2345
3163
|
static async text(messages, options = {}) {
|
|
@@ -2461,65 +3279,110 @@ var LLM = class {
|
|
|
2461
3279
|
let finalText = "";
|
|
2462
3280
|
while (iterations < maxIterations) {
|
|
2463
3281
|
iterations++;
|
|
2464
|
-
const
|
|
3282
|
+
const stream = await client.messages.create({
|
|
2465
3283
|
model: modelName,
|
|
2466
3284
|
max_tokens: options.maxTokens || 4e3,
|
|
2467
3285
|
temperature: options.temperature,
|
|
2468
3286
|
system: messages.sys,
|
|
2469
3287
|
messages: conversationMessages,
|
|
2470
|
-
tools
|
|
3288
|
+
tools,
|
|
3289
|
+
stream: true
|
|
3290
|
+
// Enable streaming
|
|
2471
3291
|
});
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
3292
|
+
let stopReason = null;
|
|
3293
|
+
const contentBlocks = [];
|
|
3294
|
+
let currentTextBlock = "";
|
|
3295
|
+
let currentToolUse = null;
|
|
3296
|
+
for await (const chunk of stream) {
|
|
3297
|
+
if (chunk.type === "message_start") {
|
|
3298
|
+
contentBlocks.length = 0;
|
|
3299
|
+
currentTextBlock = "";
|
|
3300
|
+
currentToolUse = null;
|
|
3301
|
+
}
|
|
3302
|
+
if (chunk.type === "content_block_start") {
|
|
3303
|
+
if (chunk.content_block.type === "text") {
|
|
3304
|
+
currentTextBlock = "";
|
|
3305
|
+
} else if (chunk.content_block.type === "tool_use") {
|
|
3306
|
+
currentToolUse = {
|
|
3307
|
+
type: "tool_use",
|
|
3308
|
+
id: chunk.content_block.id,
|
|
3309
|
+
name: chunk.content_block.name,
|
|
3310
|
+
input: {}
|
|
3311
|
+
};
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
if (chunk.type === "content_block_delta") {
|
|
3315
|
+
if (chunk.delta.type === "text_delta") {
|
|
3316
|
+
const text = chunk.delta.text;
|
|
3317
|
+
currentTextBlock += text;
|
|
3318
|
+
if (options.partial) {
|
|
3319
|
+
options.partial(text);
|
|
3320
|
+
}
|
|
3321
|
+
} else if (chunk.delta.type === "input_json_delta" && currentToolUse) {
|
|
3322
|
+
currentToolUse.inputJson = (currentToolUse.inputJson || "") + chunk.delta.partial_json;
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
if (chunk.type === "content_block_stop") {
|
|
3326
|
+
if (currentTextBlock) {
|
|
3327
|
+
contentBlocks.push({
|
|
3328
|
+
type: "text",
|
|
3329
|
+
text: currentTextBlock
|
|
3330
|
+
});
|
|
3331
|
+
finalText = currentTextBlock;
|
|
3332
|
+
currentTextBlock = "";
|
|
3333
|
+
} else if (currentToolUse) {
|
|
3334
|
+
try {
|
|
3335
|
+
currentToolUse.input = currentToolUse.inputJson ? JSON.parse(currentToolUse.inputJson) : {};
|
|
3336
|
+
} catch (error) {
|
|
3337
|
+
currentToolUse.input = {};
|
|
3338
|
+
}
|
|
3339
|
+
delete currentToolUse.inputJson;
|
|
3340
|
+
contentBlocks.push(currentToolUse);
|
|
3341
|
+
currentToolUse = null;
|
|
2478
3342
|
}
|
|
2479
3343
|
}
|
|
3344
|
+
if (chunk.type === "message_delta") {
|
|
3345
|
+
stopReason = chunk.delta.stop_reason || stopReason;
|
|
3346
|
+
}
|
|
3347
|
+
if (chunk.type === "message_stop") {
|
|
3348
|
+
break;
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
if (stopReason === "end_turn") {
|
|
2480
3352
|
break;
|
|
2481
3353
|
}
|
|
2482
|
-
if (
|
|
2483
|
-
const toolUses =
|
|
3354
|
+
if (stopReason === "tool_use") {
|
|
3355
|
+
const toolUses = contentBlocks.filter((block) => block.type === "tool_use");
|
|
2484
3356
|
if (toolUses.length === 0) {
|
|
2485
3357
|
break;
|
|
2486
3358
|
}
|
|
2487
3359
|
conversationMessages.push({
|
|
2488
3360
|
role: "assistant",
|
|
2489
|
-
content:
|
|
3361
|
+
content: contentBlocks
|
|
2490
3362
|
});
|
|
2491
3363
|
const toolResults = {
|
|
2492
3364
|
role: "user",
|
|
2493
3365
|
content: []
|
|
2494
3366
|
};
|
|
2495
3367
|
for (const toolUse of toolUses) {
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
});
|
|
2511
|
-
}
|
|
3368
|
+
try {
|
|
3369
|
+
const result = await toolHandler(toolUse.name, toolUse.input);
|
|
3370
|
+
toolResults.content.push({
|
|
3371
|
+
type: "tool_result",
|
|
3372
|
+
tool_use_id: toolUse.id,
|
|
3373
|
+
content: typeof result === "string" ? result : JSON.stringify(result)
|
|
3374
|
+
});
|
|
3375
|
+
} catch (error) {
|
|
3376
|
+
toolResults.content.push({
|
|
3377
|
+
type: "tool_result",
|
|
3378
|
+
tool_use_id: toolUse.id,
|
|
3379
|
+
content: error instanceof Error ? error.message : String(error),
|
|
3380
|
+
is_error: true
|
|
3381
|
+
});
|
|
2512
3382
|
}
|
|
2513
3383
|
}
|
|
2514
3384
|
conversationMessages.push(toolResults);
|
|
2515
3385
|
} else {
|
|
2516
|
-
const textBlock = response.content.find((block) => block.type === "text");
|
|
2517
|
-
if (textBlock && textBlock.type === "text") {
|
|
2518
|
-
finalText = textBlock.text;
|
|
2519
|
-
if (options.partial) {
|
|
2520
|
-
options.partial(finalText);
|
|
2521
|
-
}
|
|
2522
|
-
}
|
|
2523
3386
|
break;
|
|
2524
3387
|
}
|
|
2525
3388
|
}
|
|
@@ -2582,9 +3445,9 @@ var LLM = class {
|
|
|
2582
3445
|
// ============================================================
|
|
2583
3446
|
/**
|
|
2584
3447
|
* Parse JSON string, handling markdown code blocks and surrounding text
|
|
2585
|
-
* Enhanced version from
|
|
3448
|
+
* Enhanced version with jsonrepair to handle malformed JSON from LLMs
|
|
2586
3449
|
* @param text - Text that may contain JSON wrapped in ```json...``` or with surrounding text
|
|
2587
|
-
* @returns Parsed JSON object
|
|
3450
|
+
* @returns Parsed JSON object or array
|
|
2588
3451
|
*/
|
|
2589
3452
|
static _parseJSON(text) {
|
|
2590
3453
|
let jsonText = text.trim();
|
|
@@ -2594,11 +3457,29 @@ var LLM = class {
|
|
|
2594
3457
|
jsonText = jsonText.replace(/^```\s*\n?/, "").replace(/\n?```\s*$/, "");
|
|
2595
3458
|
}
|
|
2596
3459
|
const firstBrace = jsonText.indexOf("{");
|
|
3460
|
+
const firstBracket = jsonText.indexOf("[");
|
|
2597
3461
|
const lastBrace = jsonText.lastIndexOf("}");
|
|
2598
|
-
|
|
2599
|
-
|
|
3462
|
+
const lastBracket = jsonText.lastIndexOf("]");
|
|
3463
|
+
let startIdx = -1;
|
|
3464
|
+
let endIdx = -1;
|
|
3465
|
+
if (firstBrace !== -1 && (firstBracket === -1 || firstBrace < firstBracket)) {
|
|
3466
|
+
startIdx = firstBrace;
|
|
3467
|
+
endIdx = lastBrace;
|
|
3468
|
+
} else if (firstBracket !== -1) {
|
|
3469
|
+
startIdx = firstBracket;
|
|
3470
|
+
endIdx = lastBracket;
|
|
3471
|
+
}
|
|
3472
|
+
if (startIdx !== -1 && endIdx !== -1 && startIdx < endIdx) {
|
|
3473
|
+
jsonText = jsonText.substring(startIdx, endIdx + 1);
|
|
3474
|
+
}
|
|
3475
|
+
try {
|
|
3476
|
+
const repairedJson = jsonrepair(jsonText);
|
|
3477
|
+
return JSON.parse(repairedJson);
|
|
3478
|
+
} catch (error) {
|
|
3479
|
+
throw new Error(`Failed to parse JSON: ${error instanceof Error ? error.message : String(error)}
|
|
3480
|
+
|
|
3481
|
+
Original text: ${jsonText.substring(0, 200)}...`);
|
|
2600
3482
|
}
|
|
2601
|
-
return JSON.parse(jsonText);
|
|
2602
3483
|
}
|
|
2603
3484
|
};
|
|
2604
3485
|
|
|
@@ -3211,7 +4092,7 @@ var BaseLLM = class {
|
|
|
3211
4092
|
logger.debug(`[${this.getProviderName()}] Loaded match-text-components prompts`);
|
|
3212
4093
|
logger.file("\n=============================\nmatch text components system prompt:", prompts.system);
|
|
3213
4094
|
logCollector?.info("Matching components from text response...");
|
|
3214
|
-
const
|
|
4095
|
+
const result = await LLM.stream(
|
|
3215
4096
|
{
|
|
3216
4097
|
sys: prompts.system,
|
|
3217
4098
|
user: prompts.user
|
|
@@ -3222,60 +4103,12 @@ var BaseLLM = class {
|
|
|
3222
4103
|
temperature: 0.2,
|
|
3223
4104
|
apiKey: this.getApiKey(apiKey)
|
|
3224
4105
|
},
|
|
3225
|
-
|
|
3226
|
-
//
|
|
4106
|
+
true
|
|
4107
|
+
// Parse as JSON
|
|
3227
4108
|
);
|
|
3228
|
-
logger.debug(`[${this.getProviderName()}]
|
|
3229
|
-
let result;
|
|
3230
|
-
try {
|
|
3231
|
-
let cleanedResponse = rawResponse || "";
|
|
3232
|
-
cleanedResponse = cleanedResponse.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
|
|
3233
|
-
const jsonMatch = cleanedResponse.match(/\{[\s\S]*\}/);
|
|
3234
|
-
if (jsonMatch) {
|
|
3235
|
-
cleanedResponse = jsonMatch[0];
|
|
3236
|
-
}
|
|
3237
|
-
result = JSON.parse(cleanedResponse);
|
|
3238
|
-
} catch (parseError) {
|
|
3239
|
-
logger.error(`[${this.getProviderName()}] Failed to parse component matching JSON response`);
|
|
3240
|
-
const errorMsg = parseError instanceof Error ? parseError.message : String(parseError);
|
|
3241
|
-
const posMatch = errorMsg.match(/position (\d+)/);
|
|
3242
|
-
const errorPos = posMatch ? parseInt(posMatch[1]) : -1;
|
|
3243
|
-
if (errorPos > 0 && rawResponse) {
|
|
3244
|
-
const start = Math.max(0, errorPos - 200);
|
|
3245
|
-
const end = Math.min(rawResponse.length, errorPos + 200);
|
|
3246
|
-
logger.debug(`[${this.getProviderName()}] Error context (position ${errorPos}):`);
|
|
3247
|
-
logger.debug(rawResponse.substring(start, end));
|
|
3248
|
-
logger.debug(" ".repeat(Math.min(200, errorPos - start)) + "^--- Error here");
|
|
3249
|
-
}
|
|
3250
|
-
logger.debug(`[${this.getProviderName()}] Raw response (first 2000 chars):`, rawResponse?.substring(0, 2e3));
|
|
3251
|
-
logger.debug(`[${this.getProviderName()}] Parse error:`, parseError);
|
|
3252
|
-
logCollector?.error(`Failed to parse component matching response: ${errorMsg}`);
|
|
3253
|
-
try {
|
|
3254
|
-
logger.info(`[${this.getProviderName()}] Attempting aggressive JSON cleanup...`);
|
|
3255
|
-
let aggressive = rawResponse || "";
|
|
3256
|
-
aggressive = aggressive.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
|
|
3257
|
-
const match = aggressive.match(/\{[\s\S]*\}/);
|
|
3258
|
-
if (match) {
|
|
3259
|
-
aggressive = match[0];
|
|
3260
|
-
}
|
|
3261
|
-
aggressive = aggressive.replace(/,(\s*[}\]])/g, "$1");
|
|
3262
|
-
result = JSON.parse(aggressive);
|
|
3263
|
-
logger.info(`[${this.getProviderName()}] Aggressive cleanup succeeded!`);
|
|
3264
|
-
} catch (secondError) {
|
|
3265
|
-
logger.error(`[${this.getProviderName()}] Aggressive cleanup also failed`);
|
|
3266
|
-
return {
|
|
3267
|
-
components: [],
|
|
3268
|
-
selectedLayout: "MultiComponentContainer",
|
|
3269
|
-
selectedLayoutId: "",
|
|
3270
|
-
selectedLayoutComponent: null,
|
|
3271
|
-
layoutReasoning: "No layout selected",
|
|
3272
|
-
actions: []
|
|
3273
|
-
};
|
|
3274
|
-
}
|
|
3275
|
-
}
|
|
4109
|
+
logger.debug(`[${this.getProviderName()}] Component matching response parsed successfully`);
|
|
3276
4110
|
const matchedComponents = result.matchedComponents || [];
|
|
3277
|
-
const
|
|
3278
|
-
const selectedLayoutId = result.selectedLayoutId || "";
|
|
4111
|
+
const selectedLayoutId = result.selectedLayoutId || "multi-component-container";
|
|
3279
4112
|
const layoutReasoning = result.layoutReasoning || "No layout reasoning provided";
|
|
3280
4113
|
const rawActions = result.actions || [];
|
|
3281
4114
|
const actions = convertQuestionsToActions(rawActions);
|
|
@@ -3287,11 +4120,11 @@ var BaseLLM = class {
|
|
|
3287
4120
|
}
|
|
3288
4121
|
}
|
|
3289
4122
|
logger.info(`[${this.getProviderName()}] Matched ${matchedComponents.length} components from text response`);
|
|
3290
|
-
logger.info(`[${this.getProviderName()}] Selected layout:
|
|
4123
|
+
logger.info(`[${this.getProviderName()}] Selected layout: (ID: ${selectedLayoutId})`);
|
|
3291
4124
|
logger.info(`[${this.getProviderName()}] Layout reasoning: ${layoutReasoning}`);
|
|
3292
4125
|
logger.info(`[${this.getProviderName()}] Generated ${actions.length} follow-up actions`);
|
|
3293
4126
|
if (matchedComponents.length > 0) {
|
|
3294
|
-
logCollector?.info(`Matched ${matchedComponents.length} components for visualization
|
|
4127
|
+
logCollector?.info(`Matched ${matchedComponents.length} components for visualization `);
|
|
3295
4128
|
logCollector?.info(`Layout reasoning: ${layoutReasoning}`);
|
|
3296
4129
|
matchedComponents.forEach((comp, idx) => {
|
|
3297
4130
|
logCollector?.info(` ${idx + 1}. ${comp.componentName} (${comp.componentType}): ${comp.reasoning}`);
|
|
@@ -3326,7 +4159,6 @@ var BaseLLM = class {
|
|
|
3326
4159
|
}).filter(Boolean);
|
|
3327
4160
|
return {
|
|
3328
4161
|
components: finalComponents,
|
|
3329
|
-
selectedLayout,
|
|
3330
4162
|
selectedLayoutId,
|
|
3331
4163
|
selectedLayoutComponent,
|
|
3332
4164
|
layoutReasoning,
|
|
@@ -3334,15 +4166,13 @@ var BaseLLM = class {
|
|
|
3334
4166
|
};
|
|
3335
4167
|
} catch (error) {
|
|
3336
4168
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3337
|
-
logger.error(`[${this.getProviderName()}] Error matching components
|
|
3338
|
-
|
|
3339
|
-
logCollector?.error(`Error matching components: ${errorMsg}`);
|
|
4169
|
+
logger.error(`[${this.getProviderName()}] Error matching components: ${errorMsg}`);
|
|
4170
|
+
logCollector?.error(`Failed to match components: ${errorMsg}`);
|
|
3340
4171
|
return {
|
|
3341
4172
|
components: [],
|
|
3342
|
-
selectedLayout: "MultiComponentContainer",
|
|
3343
4173
|
selectedLayoutId: "",
|
|
3344
4174
|
selectedLayoutComponent: null,
|
|
3345
|
-
layoutReasoning: "
|
|
4175
|
+
layoutReasoning: "Failed to match components due to parsing error",
|
|
3346
4176
|
actions: []
|
|
3347
4177
|
};
|
|
3348
4178
|
}
|
|
@@ -3609,7 +4439,7 @@ ${errorMsg}
|
|
|
3609
4439
|
container_componet = {
|
|
3610
4440
|
...selectedLayoutComponent,
|
|
3611
4441
|
id: `${selectedLayoutComponent.id}_${Date.now()}`,
|
|
3612
|
-
|
|
4442
|
+
// Make ID unique for each instance
|
|
3613
4443
|
props: {
|
|
3614
4444
|
...selectedLayoutComponent.props,
|
|
3615
4445
|
config: {
|
|
@@ -6382,6 +7212,7 @@ var SuperatomSDK = class {
|
|
|
6382
7212
|
}
|
|
6383
7213
|
/**
|
|
6384
7214
|
* Initialize PromptLoader and load prompts into memory
|
|
7215
|
+
* Tries to load from file system first, then falls back to hardcoded prompts
|
|
6385
7216
|
*/
|
|
6386
7217
|
async initializePromptLoader(promptsDir) {
|
|
6387
7218
|
try {
|
|
@@ -6455,7 +7286,7 @@ var SuperatomSDK = class {
|
|
|
6455
7286
|
url.searchParams.set("projectId", this.projectId);
|
|
6456
7287
|
url.searchParams.set("userId", this.userId);
|
|
6457
7288
|
url.searchParams.set("type", this.type);
|
|
6458
|
-
logger.info(`Connecting to WebSocket: ${url.
|
|
7289
|
+
logger.info(`Connecting to WebSocket: ${url.toString()}`);
|
|
6459
7290
|
this.ws = createWebSocket(url.toString());
|
|
6460
7291
|
this.ws.addEventListener("open", () => {
|
|
6461
7292
|
this.connected = true;
|