@superatomai/sdk-node 0.0.1 → 0.0.3

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
@@ -1866,53 +1866,126 @@ import fs3 from "fs";
1866
1866
  import path3 from "path";
1867
1867
  var PromptLoader = class {
1868
1868
  constructor(config) {
1869
+ this.promptCache = /* @__PURE__ */ new Map();
1870
+ this.isInitialized = false;
1871
+ logger.debug("Initializing PromptLoader...", process.cwd());
1869
1872
  this.promptsDir = config?.promptsDir || path3.join(process.cwd(), ".prompts");
1873
+ this.defaultPromptsDir = path3.join(__dirname, "..", "..", ".prompts");
1870
1874
  }
1871
1875
  /**
1872
- * Load a single prompt file and replace variables using {{VARIABLE_NAME}} pattern
1876
+ * Initialize and cache all prompts into memory
1877
+ * This should be called once at SDK startup
1878
+ */
1879
+ async initialize() {
1880
+ if (this.isInitialized) {
1881
+ logger.debug("PromptLoader already initialized, skipping...");
1882
+ return;
1883
+ }
1884
+ logger.info("Loading prompts into memory...");
1885
+ const promptTypes = [
1886
+ "classify",
1887
+ "match-component",
1888
+ "modify-props",
1889
+ "single-component",
1890
+ "mutli-component",
1891
+ "actions",
1892
+ "container-metadata"
1893
+ ];
1894
+ for (const promptType of promptTypes) {
1895
+ try {
1896
+ const template = await this.loadPromptTemplate(promptType);
1897
+ this.promptCache.set(promptType, template);
1898
+ logger.debug(`Cached prompt: ${promptType}`);
1899
+ } catch (error) {
1900
+ logger.error(`Failed to load prompt '${promptType}':`, error);
1901
+ throw error;
1902
+ }
1903
+ }
1904
+ this.isInitialized = true;
1905
+ logger.info(`Successfully loaded ${this.promptCache.size} prompt templates into memory`);
1906
+ }
1907
+ /**
1908
+ * Load a prompt template from file system (tries custom dir first, then defaults to SDK dir)
1873
1909
  * @param promptName - Name of the prompt folder
1874
- * @param promptType - Type of prompt ('system' or 'user')
1875
- * @param variables - Variables to replace in the template
1876
- * @returns Processed prompt string
1910
+ * @returns Template with system and user prompts
1877
1911
  */
1878
- async loadPrompt(promptName, promptType, variables) {
1879
- try {
1880
- const promptPath = path3.join(
1881
- this.promptsDir,
1882
- promptName,
1883
- `${promptType}.md`
1884
- );
1885
- let content = fs3.readFileSync(promptPath, "utf-8");
1886
- for (const [key, value] of Object.entries(variables)) {
1887
- const pattern = new RegExp(`{{${key}}}`, "g");
1888
- const replacementValue = typeof value === "string" ? value : JSON.stringify(value);
1889
- content = content.replace(pattern, replacementValue);
1912
+ async loadPromptTemplate(promptName) {
1913
+ const tryLoadFromDir = (dir) => {
1914
+ try {
1915
+ const systemPath = path3.join(dir, promptName, "system.md");
1916
+ const userPath = path3.join(dir, promptName, "user.md");
1917
+ if (fs3.existsSync(systemPath) && fs3.existsSync(userPath)) {
1918
+ const system = fs3.readFileSync(systemPath, "utf-8");
1919
+ const user = fs3.readFileSync(userPath, "utf-8");
1920
+ logger.debug(`Loaded prompt '${promptName}' from ${dir}`);
1921
+ return { system, user };
1922
+ }
1923
+ return null;
1924
+ } catch (error) {
1925
+ return null;
1890
1926
  }
1891
- return content;
1892
- } catch (error) {
1893
- console.error(`Error loading prompt '${promptName}/${promptType}.md':`, error);
1894
- throw error;
1927
+ };
1928
+ let template = tryLoadFromDir(this.promptsDir);
1929
+ if (!template) {
1930
+ logger.warn(`Prompt '${promptName}' not found in ${this.promptsDir}, trying default location...`);
1931
+ template = tryLoadFromDir(this.defaultPromptsDir);
1932
+ }
1933
+ if (!template) {
1934
+ throw new Error(`Prompt template '${promptName}' not found in either ${this.promptsDir} or ${this.defaultPromptsDir}`);
1895
1935
  }
1936
+ return template;
1896
1937
  }
1897
1938
  /**
1898
- * Load both system and user prompts and replace variables
1939
+ * Replace variables in a template string using {{VARIABLE_NAME}} pattern
1940
+ * @param template - Template string with placeholders
1941
+ * @param variables - Variables to replace in the template
1942
+ * @returns Processed string
1943
+ */
1944
+ replaceVariables(template, variables) {
1945
+ let content = template;
1946
+ for (const [key, value] of Object.entries(variables)) {
1947
+ const pattern = new RegExp(`{{${key}}}`, "g");
1948
+ const replacementValue = typeof value === "string" ? value : JSON.stringify(value);
1949
+ content = content.replace(pattern, replacementValue);
1950
+ }
1951
+ return content;
1952
+ }
1953
+ /**
1954
+ * Load both system and user prompts from cache and replace variables
1899
1955
  * @param promptName - Name of the prompt folder
1900
1956
  * @param variables - Variables to replace in the templates
1901
1957
  * @returns Object containing both system and user prompts
1902
1958
  */
1903
1959
  async loadPrompts(promptName, variables) {
1904
- const [system, user] = await Promise.all([
1905
- this.loadPrompt(promptName, "system", variables),
1906
- this.loadPrompt(promptName, "user", variables)
1907
- ]);
1908
- return { system, user };
1960
+ if (!this.isInitialized) {
1961
+ logger.warn("PromptLoader not initialized, loading prompts on-demand (not recommended)");
1962
+ await this.initialize();
1963
+ }
1964
+ const template = this.promptCache.get(promptName);
1965
+ if (!template) {
1966
+ throw new Error(`Prompt template '${promptName}' not found in cache. Available prompts: ${Array.from(this.promptCache.keys()).join(", ")}`);
1967
+ }
1968
+ return {
1969
+ system: this.replaceVariables(template.system, variables),
1970
+ user: this.replaceVariables(template.user, variables)
1971
+ };
1909
1972
  }
1910
1973
  /**
1911
- * Set custom prompts directory
1974
+ * DEPRECATED: Use loadPrompts instead
1975
+ * Load a single prompt file and replace variables using {{VARIABLE_NAME}} pattern
1976
+ */
1977
+ async loadPrompt(promptName, promptType, variables) {
1978
+ const prompts = await this.loadPrompts(promptName, variables);
1979
+ return promptType === "system" ? prompts.system : prompts.user;
1980
+ }
1981
+ /**
1982
+ * Set custom prompts directory (requires re-initialization)
1912
1983
  * @param dir - Path to the prompts directory
1913
1984
  */
1914
1985
  setPromptsDir(dir) {
1915
1986
  this.promptsDir = dir;
1987
+ this.isInitialized = false;
1988
+ this.promptCache.clear();
1916
1989
  }
1917
1990
  /**
1918
1991
  * Get current prompts directory
@@ -1921,8 +1994,23 @@ var PromptLoader = class {
1921
1994
  getPromptsDir() {
1922
1995
  return this.promptsDir;
1923
1996
  }
1997
+ /**
1998
+ * Check if prompts are loaded in memory
1999
+ */
2000
+ isReady() {
2001
+ return this.isInitialized;
2002
+ }
2003
+ /**
2004
+ * Get the number of cached prompts
2005
+ */
2006
+ getCacheSize() {
2007
+ return this.promptCache.size;
2008
+ }
1924
2009
  };
1925
- var promptLoader = new PromptLoader();
2010
+ var defaultPromptsPath = process.env.PROMPTS_DIR || path3.join(process.cwd(), ".prompts");
2011
+ var promptLoader = new PromptLoader({
2012
+ promptsDir: defaultPromptsPath
2013
+ });
1926
2014
 
1927
2015
  // src/llm.ts
1928
2016
  import Anthropic from "@anthropic-ai/sdk";
@@ -1960,7 +2048,7 @@ var LLM = class {
1960
2048
  *
1961
2049
  * @example
1962
2050
  * "anthropic/claude-sonnet-4-5" → ["anthropic", "claude-sonnet-4-5"]
1963
- * "groq/gpt-oss-120b" → ["groq", "gpt-oss-120b"]
2051
+ * "groq/openai/gpt-oss-120b" → ["groq", "openai/gpt-oss-120b"]
1964
2052
  * "claude-sonnet-4-5" → ["anthropic", "claude-sonnet-4-5"] (default)
1965
2053
  */
1966
2054
  static _parseModel(modelString) {
@@ -1968,8 +2056,10 @@ var LLM = class {
1968
2056
  return ["anthropic", "claude-sonnet-4-5"];
1969
2057
  }
1970
2058
  if (modelString.includes("/")) {
1971
- const [provider, model] = modelString.split("/");
1972
- return [provider.toLowerCase().trim(), model.trim()];
2059
+ const firstSlashIndex = modelString.indexOf("/");
2060
+ const provider = modelString.substring(0, firstSlashIndex).toLowerCase().trim();
2061
+ const model = modelString.substring(firstSlashIndex + 1).trim();
2062
+ return [provider, model];
1973
2063
  }
1974
2064
  return ["anthropic", modelString];
1975
2065
  }
@@ -1977,8 +2067,9 @@ var LLM = class {
1977
2067
  // ANTHROPIC IMPLEMENTATION
1978
2068
  // ============================================================
1979
2069
  static async _anthropicText(messages, modelName, options) {
2070
+ const apiKey = options.apiKey || process.env.ANTHROPIC_API_KEY || "";
1980
2071
  const client = new Anthropic({
1981
- apiKey: options.apiKey || process.env.ANTHROPIC_API_KEY || ""
2072
+ apiKey
1982
2073
  });
1983
2074
  const response = await client.messages.create({
1984
2075
  model: modelName,
@@ -1994,8 +2085,9 @@ var LLM = class {
1994
2085
  return textBlock?.type === "text" ? textBlock.text : "";
1995
2086
  }
1996
2087
  static async _anthropicStream(messages, modelName, options, json) {
2088
+ const apiKey = options.apiKey || process.env.ANTHROPIC_API_KEY || "";
1997
2089
  const client = new Anthropic({
1998
- apiKey: options.apiKey || process.env.ANTHROPIC_API_KEY || ""
2090
+ apiKey
1999
2091
  });
2000
2092
  const stream = await client.messages.create({
2001
2093
  model: modelName,
@@ -2042,8 +2134,9 @@ var LLM = class {
2042
2134
  return response.choices[0]?.message?.content || "";
2043
2135
  }
2044
2136
  static async _groqStream(messages, modelName, options, json) {
2137
+ const apiKey = options.apiKey || process.env.GROQ_API_KEY || "";
2045
2138
  const client = new Groq({
2046
- apiKey: options.apiKey || process.env.GROQ_API_KEY || ""
2139
+ apiKey
2047
2140
  });
2048
2141
  const stream = await client.chat.completions.create({
2049
2142
  model: modelName,
@@ -2113,10 +2206,8 @@ var BaseLLM = class {
2113
2206
  * Classify user question to determine the type and required visualizations
2114
2207
  */
2115
2208
  async classifyUserQuestion(userPrompt, apiKey, logCollector, conversationHistory) {
2116
- const schemaDoc = schema.generateSchemaDocumentation();
2117
2209
  try {
2118
2210
  const prompts = await promptLoader.loadPrompts("classify", {
2119
- SCHEMA_DOC: schemaDoc || "No schema available",
2120
2211
  USER_PROMPT: userPrompt,
2121
2212
  CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
2122
2213
  });
@@ -2171,6 +2262,7 @@ var BaseLLM = class {
2171
2262
  CURRENT_PROPS: JSON.stringify(originalProps, null, 2),
2172
2263
  CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
2173
2264
  });
2265
+ logger.debug("props-modification: System prompt\n", prompts.system.substring(0, 100), "\n\n\n", "User prompt:", prompts.user.substring(0, 50));
2174
2266
  const result = await LLM.stream(
2175
2267
  {
2176
2268
  sys: prompts.system,
@@ -2218,21 +2310,47 @@ var BaseLLM = class {
2218
2310
  }
2219
2311
  }
2220
2312
  /**
2221
- * Generate a dynamic component for analytical questions when no matching component exists
2222
- * This creates a custom component with appropriate visualization and query
2313
+ * Match and select a component from available components filtered by type
2314
+ * This picks the best matching component based on user prompt and modifies its props
2223
2315
  */
2224
- async generateAnalyticalComponent(userPrompt, preferredVisualizationType, apiKey, logCollector, conversationHistory) {
2225
- const schemaDoc = schema.generateSchemaDocumentation();
2316
+ async generateAnalyticalComponent(userPrompt, components, preferredVisualizationType, apiKey, logCollector, conversationHistory) {
2226
2317
  try {
2318
+ const filteredComponents = preferredVisualizationType ? components.filter((c) => c.type === preferredVisualizationType) : components;
2319
+ if (filteredComponents.length === 0) {
2320
+ logCollector?.warn(
2321
+ `No components found of type ${preferredVisualizationType}`,
2322
+ "explanation",
2323
+ { reason: "No matching components available for this visualization type" }
2324
+ );
2325
+ return {
2326
+ component: null,
2327
+ reasoning: `No components available of type ${preferredVisualizationType}`,
2328
+ isGenerated: false
2329
+ };
2330
+ }
2331
+ const componentsText = filteredComponents.map((comp, idx) => {
2332
+ const keywords = comp.keywords ? comp.keywords.join(", ") : "";
2333
+ const category = comp.category || "general";
2334
+ const propsPreview = comp.props ? JSON.stringify(comp.props, null, 2) : "No props";
2335
+ return `${idx + 1}. ID: ${comp.id}
2336
+ Name: ${comp.name}
2337
+ Type: ${comp.type}
2338
+ Category: ${category}
2339
+ Description: ${comp.description || "No description"}
2340
+ Keywords: ${keywords}
2341
+ Props Preview: ${propsPreview}`;
2342
+ }).join("\n\n");
2227
2343
  const visualizationConstraint = preferredVisualizationType ? `
2228
- **IMPORTANT: The user has specifically requested a ${preferredVisualizationType} visualization. You MUST use this type.**
2344
+ **IMPORTANT: Components are filtered to type ${preferredVisualizationType}. Select the best match.**
2229
2345
  ` : "";
2230
2346
  const prompts = await promptLoader.loadPrompts("single-component", {
2231
- SCHEMA_DOC: schemaDoc || "No schema available",
2347
+ COMPONENT_TYPE: preferredVisualizationType || "any",
2348
+ COMPONENTS_LIST: componentsText,
2232
2349
  VISUALIZATION_CONSTRAINT: visualizationConstraint,
2233
2350
  USER_PROMPT: userPrompt,
2234
2351
  CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
2235
2352
  });
2353
+ logger.debug("single-component: System prompt\n", prompts.system.substring(0, 100), "\n\n\n", "User prompt:", prompts.user.substring(0, 50));
2236
2354
  const result = await LLM.stream(
2237
2355
  {
2238
2356
  sys: prompts.system,
@@ -2247,53 +2365,63 @@ var BaseLLM = class {
2247
2365
  true
2248
2366
  // Parse as JSON
2249
2367
  );
2250
- if (!result.canGenerate) {
2368
+ if (!result.canGenerate || result.confidence < 50) {
2251
2369
  logCollector?.warn(
2252
- "Cannot generate component",
2370
+ "Cannot match component",
2253
2371
  "explanation",
2254
- { reason: result.reasoning || "Unable to generate component for this question" }
2372
+ { reason: result.reasoning || "Unable to find matching component for this question" }
2255
2373
  );
2256
2374
  return {
2257
2375
  component: null,
2258
- reasoning: result.reasoning || "Unable to generate component for this question",
2376
+ reasoning: result.reasoning || "Unable to find matching component for this question",
2259
2377
  isGenerated: false
2260
2378
  };
2261
2379
  }
2262
- const query = ensureQueryLimit(result.query, this.defaultLimit);
2263
- logCollector?.logQuery(
2264
- "Analytical component query generated",
2265
- query,
2266
- {
2267
- componentType: result.componentType,
2268
- visualization: preferredVisualizationType || result.componentType,
2269
- title: result.title
2270
- }
2380
+ const componentIndex = result.componentIndex;
2381
+ const componentId = result.componentId;
2382
+ let matchedComponent = null;
2383
+ if (componentId) {
2384
+ matchedComponent = filteredComponents.find((c) => c.id === componentId);
2385
+ }
2386
+ if (!matchedComponent && componentIndex) {
2387
+ matchedComponent = filteredComponents[componentIndex - 1];
2388
+ }
2389
+ if (!matchedComponent) {
2390
+ logCollector?.warn("Component not found in filtered list");
2391
+ return {
2392
+ component: null,
2393
+ reasoning: "Component not found in filtered list",
2394
+ isGenerated: false
2395
+ };
2396
+ }
2397
+ logCollector?.info(`Matched component: ${matchedComponent.name} (confidence: ${result.confidence}%)`);
2398
+ const propsValidation = await this.validateAndModifyProps(
2399
+ userPrompt,
2400
+ matchedComponent.props,
2401
+ matchedComponent.name,
2402
+ matchedComponent.type,
2403
+ matchedComponent.description,
2404
+ apiKey,
2405
+ logCollector,
2406
+ conversationHistory
2271
2407
  );
2408
+ const modifiedComponent = {
2409
+ ...matchedComponent,
2410
+ props: propsValidation.props
2411
+ };
2272
2412
  logCollector?.logExplanation(
2273
- "Analytical component generated",
2274
- result.reasoning || "Generated dynamic component based on analytical question",
2413
+ "Analytical component selected and modified",
2414
+ result.reasoning || "Selected component based on analytical question",
2275
2415
  {
2276
- componentType: result.componentType,
2277
- description: result.description
2416
+ componentName: matchedComponent.name,
2417
+ componentType: matchedComponent.type,
2418
+ confidence: result.confidence,
2419
+ propsModified: propsValidation.isModified
2278
2420
  }
2279
2421
  );
2280
- const dynamicComponent = {
2281
- id: `dynamic_${Date.now()}`,
2282
- name: `Dynamic${result.componentType}`,
2283
- type: result.componentType,
2284
- description: result.description,
2285
- category: "dynamic",
2286
- keywords: [],
2287
- props: {
2288
- query,
2289
- title: result.title,
2290
- description: result.description,
2291
- config: result.config || {}
2292
- }
2293
- };
2294
2422
  return {
2295
- component: dynamicComponent,
2296
- reasoning: result.reasoning || "Generated dynamic component based on analytical question",
2423
+ component: modifiedComponent,
2424
+ reasoning: result.reasoning || "Selected and modified component based on analytical question",
2297
2425
  isGenerated: true
2298
2426
  };
2299
2427
  } catch (error) {
@@ -2301,6 +2429,51 @@ var BaseLLM = class {
2301
2429
  throw error;
2302
2430
  }
2303
2431
  }
2432
+ /**
2433
+ * Generate container metadata (title and description) for multi-component dashboard
2434
+ */
2435
+ async generateContainerMetadata(userPrompt, visualizationTypes, apiKey, logCollector, conversationHistory) {
2436
+ try {
2437
+ const prompts = await promptLoader.loadPrompts("container-metadata", {
2438
+ USER_PROMPT: userPrompt,
2439
+ VISUALIZATION_TYPES: visualizationTypes.join(", "),
2440
+ CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
2441
+ });
2442
+ const result = await LLM.stream(
2443
+ {
2444
+ sys: prompts.system,
2445
+ user: prompts.user
2446
+ },
2447
+ {
2448
+ model: this.model,
2449
+ maxTokens: 500,
2450
+ temperature: 0.3,
2451
+ apiKey: this.getApiKey(apiKey)
2452
+ },
2453
+ true
2454
+ // Parse as JSON
2455
+ );
2456
+ logCollector?.logExplanation(
2457
+ "Container metadata generated",
2458
+ `Generated title and description for multi-component dashboard`,
2459
+ {
2460
+ title: result.title,
2461
+ description: result.description,
2462
+ visualizationTypes
2463
+ }
2464
+ );
2465
+ return {
2466
+ title: result.title || `${userPrompt} - Dashboard`,
2467
+ description: result.description || `Multi-component dashboard showing ${visualizationTypes.join(", ")}`
2468
+ };
2469
+ } catch (error) {
2470
+ console.error("Error generating container metadata:", error);
2471
+ return {
2472
+ title: `${userPrompt} - Dashboard`,
2473
+ description: `Multi-component dashboard showing ${visualizationTypes.join(", ")}`
2474
+ };
2475
+ }
2476
+ }
2304
2477
  /**
2305
2478
  * Match component from a list with enhanced props modification
2306
2479
  */
@@ -2362,12 +2535,12 @@ var BaseLLM = class {
2362
2535
  const noMatchMsg = `No matching component found (confidence: ${confidence}%)`;
2363
2536
  console.log("\u2717", noMatchMsg);
2364
2537
  logCollector?.warn(noMatchMsg);
2365
- const genMsg = "Attempting to generate dynamic component from analytical question...";
2538
+ const genMsg = "Attempting to match component from analytical question...";
2366
2539
  console.log("\u2713", genMsg);
2367
2540
  logCollector?.info(genMsg);
2368
- const generatedResult = await this.generateAnalyticalComponent(userPrompt, void 0, apiKey, logCollector, conversationHistory);
2541
+ const generatedResult = await this.generateAnalyticalComponent(userPrompt, components, void 0, apiKey, logCollector, conversationHistory);
2369
2542
  if (generatedResult.component) {
2370
- const genSuccessMsg = `Successfully generated component: ${generatedResult.component.name}`;
2543
+ const genSuccessMsg = `Successfully matched component: ${generatedResult.component.name}`;
2371
2544
  logCollector?.info(genSuccessMsg);
2372
2545
  return {
2373
2546
  component: generatedResult.component,
@@ -2379,10 +2552,10 @@ var BaseLLM = class {
2379
2552
  queryModified: false
2380
2553
  };
2381
2554
  }
2382
- logCollector?.error("Failed to generate dynamic component");
2555
+ logCollector?.error("Failed to match component");
2383
2556
  return {
2384
2557
  component: null,
2385
- reasoning: result.reasoning || "No matching component found and unable to generate dynamic component",
2558
+ reasoning: result.reasoning || "No matching component found and unable to match component",
2386
2559
  method: `${this.getProviderName()}-llm`,
2387
2560
  confidence
2388
2561
  };
@@ -2430,15 +2603,15 @@ var BaseLLM = class {
2430
2603
  }
2431
2604
  }
2432
2605
  /**
2433
- * Generate multiple dynamic components for analytical questions
2606
+ * Match multiple components for analytical questions by visualization types
2434
2607
  * This is used when the user needs multiple visualizations
2435
2608
  */
2436
- async generateMultipleAnalyticalComponents(userPrompt, visualizationTypes, apiKey, logCollector, conversationHistory) {
2609
+ async generateMultipleAnalyticalComponents(userPrompt, availableComponents, visualizationTypes, apiKey, logCollector, conversationHistory) {
2437
2610
  try {
2438
- console.log("\u2713 Generating multiple components:", visualizationTypes);
2611
+ console.log("\u2713 Matching multiple components:", visualizationTypes);
2439
2612
  const components = [];
2440
2613
  for (const vizType of visualizationTypes) {
2441
- const result = await this.generateAnalyticalComponent(userPrompt, vizType, apiKey, logCollector, conversationHistory);
2614
+ const result = await this.generateAnalyticalComponent(userPrompt, availableComponents, vizType, apiKey, logCollector, conversationHistory);
2442
2615
  if (result.component) {
2443
2616
  components.push(result.component);
2444
2617
  }
@@ -2446,75 +2619,45 @@ var BaseLLM = class {
2446
2619
  if (components.length === 0) {
2447
2620
  return {
2448
2621
  components: [],
2449
- reasoning: "Failed to generate any components",
2622
+ reasoning: "Failed to match any components",
2450
2623
  isGenerated: false
2451
2624
  };
2452
2625
  }
2453
2626
  return {
2454
2627
  components,
2455
- reasoning: `Generated ${components.length} components: ${visualizationTypes.join(", ")}`,
2628
+ reasoning: `Matched ${components.length} components: ${visualizationTypes.join(", ")}`,
2456
2629
  isGenerated: true
2457
2630
  };
2458
2631
  } catch (error) {
2459
- console.error("Error generating multiple analytical components:", error);
2632
+ console.error("Error matching multiple analytical components:", error);
2460
2633
  return {
2461
2634
  components: [],
2462
- reasoning: "Error occurred while generating components",
2635
+ reasoning: "Error occurred while matching components",
2463
2636
  isGenerated: false
2464
2637
  };
2465
2638
  }
2466
2639
  }
2467
2640
  /**
2468
- * Generate a complete multi-component response with intelligent container and component props
2641
+ * Match multiple components and wrap them in a container
2469
2642
  */
2470
- async generateMultiComponentResponse(userPrompt, visualizationTypes, apiKey, logCollector, conversationHistory) {
2471
- const schemaDoc = schema.generateSchemaDocumentation();
2643
+ async generateMultiComponentResponse(userPrompt, availableComponents, visualizationTypes, apiKey, logCollector, conversationHistory) {
2472
2644
  try {
2473
- const prompts = await promptLoader.loadPrompts("mutli-component", {
2474
- SCHEMA_DOC: schemaDoc || "No schema available",
2475
- DEFAULT_LIMIT: this.defaultLimit,
2476
- USER_PROMPT: userPrompt,
2477
- VISUALIZATION_TYPES: visualizationTypes.join(", "),
2478
- CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
2479
- });
2480
- const result = await LLM.stream(
2481
- {
2482
- sys: prompts.system,
2483
- user: prompts.user
2484
- },
2485
- {
2486
- model: this.model,
2487
- maxTokens: 3e3,
2488
- temperature: 0.2,
2489
- apiKey: this.getApiKey(apiKey)
2490
- },
2491
- true
2492
- // Parse as JSON
2645
+ const matchResult = await this.generateMultipleAnalyticalComponents(
2646
+ userPrompt,
2647
+ availableComponents,
2648
+ visualizationTypes,
2649
+ apiKey,
2650
+ logCollector,
2651
+ conversationHistory
2493
2652
  );
2494
- if (!result.canGenerate || !result.components || result.components.length === 0) {
2653
+ if (!matchResult.isGenerated || matchResult.components.length === 0) {
2495
2654
  return {
2496
2655
  containerComponent: null,
2497
- reasoning: result.reasoning || "Unable to generate multi-component dashboard",
2656
+ reasoning: matchResult.reasoning || "Unable to match multi-component dashboard",
2498
2657
  isGenerated: false
2499
2658
  };
2500
2659
  }
2501
- const generatedComponents = result.components.map((compData, index) => {
2502
- const query = ensureQueryLimit(compData.query, this.defaultLimit);
2503
- return {
2504
- id: `dynamic_${compData.componentType.toLowerCase()}_${Date.now()}_${index}`,
2505
- name: `Dynamic${compData.componentType}`,
2506
- type: compData.componentType,
2507
- description: compData.description,
2508
- category: "dynamic",
2509
- keywords: [],
2510
- props: {
2511
- query,
2512
- title: compData.title,
2513
- description: compData.description,
2514
- config: compData.config || {}
2515
- }
2516
- };
2517
- });
2660
+ const generatedComponents = matchResult.components;
2518
2661
  generatedComponents.forEach((component, index) => {
2519
2662
  if (component.props.query) {
2520
2663
  logCollector?.logQuery(
@@ -2529,21 +2672,24 @@ var BaseLLM = class {
2529
2672
  );
2530
2673
  }
2531
2674
  });
2675
+ const containerTitle = `${userPrompt} - Dashboard`;
2676
+ const containerDescription = `Multi-component dashboard showing ${visualizationTypes.join(", ")}`;
2532
2677
  logCollector?.logExplanation(
2533
- "Multi-component dashboard generated",
2534
- result.reasoning || `Generated ${generatedComponents.length} components for comprehensive analysis`,
2678
+ "Multi-component dashboard matched",
2679
+ matchResult.reasoning || `Matched ${generatedComponents.length} components for comprehensive analysis`,
2535
2680
  {
2536
2681
  totalComponents: generatedComponents.length,
2537
2682
  componentTypes: generatedComponents.map((c) => c.type),
2538
- containerTitle: result.containerTitle,
2539
- containerDescription: result.containerDescription
2683
+ componentNames: generatedComponents.map((c) => c.name),
2684
+ containerTitle,
2685
+ containerDescription
2540
2686
  }
2541
2687
  );
2542
2688
  const containerComponent = {
2543
2689
  id: `multi_container_${Date.now()}`,
2544
2690
  name: "MultiComponentContainer",
2545
2691
  type: "Container",
2546
- description: result.containerDescription,
2692
+ description: containerDescription,
2547
2693
  category: "dynamic",
2548
2694
  keywords: ["multi", "container", "dashboard"],
2549
2695
  props: {
@@ -2551,14 +2697,14 @@ var BaseLLM = class {
2551
2697
  components: generatedComponents,
2552
2698
  layout: "grid",
2553
2699
  spacing: 24,
2554
- title: result.containerTitle,
2555
- description: result.containerDescription
2700
+ title: containerTitle,
2701
+ description: containerDescription
2556
2702
  }
2557
2703
  }
2558
2704
  };
2559
2705
  return {
2560
2706
  containerComponent,
2561
- reasoning: result.reasoning || `Generated multi-component dashboard with ${generatedComponents.length} components`,
2707
+ reasoning: matchResult.reasoning || `Matched multi-component dashboard with ${generatedComponents.length} components`,
2562
2708
  isGenerated: true
2563
2709
  };
2564
2710
  } catch (error) {
@@ -2579,41 +2725,89 @@ var BaseLLM = class {
2579
2725
  const classInfo = `Question type: ${classification.questionType}, Visualizations: ${classification.visualizations.join(", ") || "None"}, Multiple components: ${classification.needsMultipleComponents}`;
2580
2726
  logCollector?.info(classInfo);
2581
2727
  if (classification.questionType === "analytical") {
2582
- if (classification.visualizations.length > 0) {
2583
- if (classification.needsMultipleComponents && classification.visualizations.length > 1) {
2584
- const multiMsg = "Generating multi-component dashboard...";
2585
- logCollector?.info(multiMsg);
2586
- const result = await this.generateMultiComponentResponse(
2728
+ if (classification.visualizations.length > 1) {
2729
+ const multiMsg = `Matching ${classification.visualizations.length} components for types: ${classification.visualizations.join(", ")}`;
2730
+ logCollector?.info(multiMsg);
2731
+ const matchedComponents = [];
2732
+ for (const vizType of classification.visualizations) {
2733
+ logCollector?.info(`Matching component for type: ${vizType}`);
2734
+ const result = await this.generateAnalyticalComponent(
2587
2735
  userPrompt,
2588
- classification.visualizations,
2736
+ components,
2737
+ vizType,
2589
2738
  apiKey,
2590
2739
  logCollector,
2591
2740
  conversationHistory
2592
2741
  );
2742
+ if (result.component) {
2743
+ matchedComponents.push(result.component);
2744
+ logCollector?.info(`Matched: ${result.component.name}`);
2745
+ } else {
2746
+ logCollector?.warn(`Failed to match component for type: ${vizType}`);
2747
+ }
2748
+ }
2749
+ if (matchedComponents.length === 0) {
2593
2750
  return {
2594
- component: result.containerComponent,
2595
- reasoning: result.reasoning,
2596
- method: "classification-multi-generated",
2751
+ component: null,
2752
+ reasoning: "Failed to match any components for the requested visualization types",
2753
+ method: "classification-multi-failed",
2597
2754
  questionType: classification.questionType,
2598
2755
  needsMultipleComponents: true,
2599
2756
  propsModified: false,
2600
2757
  queryModified: false
2601
2758
  };
2602
- } else {
2603
- const vizType = classification.visualizations[0];
2604
- const result = await this.generateAnalyticalComponent(userPrompt, vizType, apiKey, logCollector, conversationHistory);
2605
- return {
2606
- component: result.component,
2607
- reasoning: result.reasoning,
2608
- method: "classification-generated",
2609
- questionType: classification.questionType,
2610
- needsMultipleComponents: false,
2611
- propsModified: false,
2612
- queryModified: false
2613
- };
2614
2759
  }
2760
+ logCollector?.info("Generating container metadata...");
2761
+ const containerMetadata = await this.generateContainerMetadata(
2762
+ userPrompt,
2763
+ classification.visualizations,
2764
+ apiKey,
2765
+ logCollector,
2766
+ conversationHistory
2767
+ );
2768
+ const containerComponent = {
2769
+ id: `multi_container_${Date.now()}`,
2770
+ name: "MultiComponentContainer",
2771
+ type: "Container",
2772
+ description: containerMetadata.description,
2773
+ category: "dynamic",
2774
+ keywords: ["multi", "container", "dashboard"],
2775
+ props: {
2776
+ config: {
2777
+ components: matchedComponents,
2778
+ layout: "grid",
2779
+ spacing: 24,
2780
+ title: containerMetadata.title,
2781
+ description: containerMetadata.description
2782
+ }
2783
+ }
2784
+ };
2785
+ logCollector?.info(`Created multi-component container with ${matchedComponents.length} components: "${containerMetadata.title}"`);
2786
+ return {
2787
+ component: containerComponent,
2788
+ reasoning: `Matched ${matchedComponents.length} components for visualization types: ${classification.visualizations.join(", ")}`,
2789
+ method: "classification-multi-generated",
2790
+ questionType: classification.questionType,
2791
+ needsMultipleComponents: true,
2792
+ propsModified: false,
2793
+ queryModified: false
2794
+ };
2795
+ } else if (classification.visualizations.length === 1) {
2796
+ const vizType = classification.visualizations[0];
2797
+ logCollector?.info(`Matching single component for type: ${vizType}`);
2798
+ const result = await this.generateAnalyticalComponent(userPrompt, components, vizType, apiKey, logCollector, conversationHistory);
2799
+ return {
2800
+ component: result.component,
2801
+ reasoning: result.reasoning,
2802
+ method: "classification-generated",
2803
+ questionType: classification.questionType,
2804
+ needsMultipleComponents: false,
2805
+ propsModified: false,
2806
+ queryModified: false
2807
+ };
2615
2808
  } else {
2616
- const result = await this.generateAnalyticalComponent(userPrompt, void 0, apiKey, logCollector, conversationHistory);
2809
+ logCollector?.info("No specific visualization type - matching from all components");
2810
+ const result = await this.generateAnalyticalComponent(userPrompt, components, void 0, apiKey, logCollector, conversationHistory);
2617
2811
  return {
2618
2812
  component: result.component,
2619
2813
  reasoning: result.reasoning,
@@ -2624,7 +2818,7 @@ var BaseLLM = class {
2624
2818
  queryModified: false
2625
2819
  };
2626
2820
  }
2627
- } else if (classification.questionType === "data_modification") {
2821
+ } else if (classification.questionType === "data_modification" || classification.questionType === "general") {
2628
2822
  const matchMsg = "Using component matching for data modification...";
2629
2823
  logCollector?.info(matchMsg);
2630
2824
  const matchResult = await this.matchComponent(userPrompt, components, apiKey, logCollector, conversationHistory);
@@ -2770,8 +2964,14 @@ var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conver
2770
2964
  logCollector?.error(emptyMsg);
2771
2965
  return { success: false, reason: emptyMsg };
2772
2966
  }
2773
- const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory);
2774
- return { success: true, data: matchResult };
2967
+ try {
2968
+ const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory);
2969
+ return { success: true, data: matchResult };
2970
+ } catch (error) {
2971
+ const errorMsg = error instanceof Error ? error.message : String(error);
2972
+ logCollector?.error(`Anthropic method failed: ${errorMsg}`);
2973
+ throw error;
2974
+ }
2775
2975
  };
2776
2976
  var useGroqMethod = async (prompt, components, apiKey, logCollector, conversationHistory) => {
2777
2977
  const msg = "Using Groq LLM matching method...";
@@ -2782,8 +2982,14 @@ var useGroqMethod = async (prompt, components, apiKey, logCollector, conversatio
2782
2982
  logCollector?.error(emptyMsg);
2783
2983
  return { success: false, reason: emptyMsg };
2784
2984
  }
2785
- const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory);
2786
- return { success: true, data: matchResult };
2985
+ try {
2986
+ const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory);
2987
+ return { success: true, data: matchResult };
2988
+ } catch (error) {
2989
+ const errorMsg = error instanceof Error ? error.message : String(error);
2990
+ logCollector?.error(`Groq method failed: ${errorMsg}`);
2991
+ throw error;
2992
+ }
2787
2993
  };
2788
2994
  var getUserResponseFromCache = async (prompt) => {
2789
2995
  return false;
@@ -3004,6 +3210,7 @@ var CONTEXT_CONFIG = {
3004
3210
  };
3005
3211
 
3006
3212
  // src/handlers/user-prompt-request.ts
3213
+ var processedMessageIds = /* @__PURE__ */ new Set();
3007
3214
  async function handleUserPromptRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders) {
3008
3215
  try {
3009
3216
  const userPromptRequest = UserPromptRequestMessageSchema.parse(data);
@@ -3011,6 +3218,18 @@ async function handleUserPromptRequest(data, components, sendMessage, anthropicA
3011
3218
  const prompt = payload.prompt;
3012
3219
  const SA_RUNTIME = payload.SA_RUNTIME;
3013
3220
  const wsId = userPromptRequest.from.id || "unknown";
3221
+ logger.info(`[REQUEST ${id}] Processing user prompt: "${prompt.substring(0, 50)}..."`);
3222
+ if (processedMessageIds.has(id)) {
3223
+ logger.warn(`[REQUEST ${id}] Duplicate request detected - ignoring`);
3224
+ return;
3225
+ }
3226
+ processedMessageIds.add(id);
3227
+ if (processedMessageIds.size > 100) {
3228
+ const firstId = processedMessageIds.values().next().value;
3229
+ if (firstId) {
3230
+ processedMessageIds.delete(firstId);
3231
+ }
3232
+ }
3014
3233
  if (!SA_RUNTIME) {
3015
3234
  sendDataResponse4(id, {
3016
3235
  success: false,
@@ -3050,7 +3269,6 @@ async function handleUserPromptRequest(data, components, sendMessage, anthropicA
3050
3269
  return;
3051
3270
  }
3052
3271
  logCollector.info(`Starting user prompt request with ${components.length} components`);
3053
- logger.info(`components length: ${components.length}`);
3054
3272
  const threadManager = ThreadManager.getInstance();
3055
3273
  let thread = threadManager.getThread(threadId);
3056
3274
  if (!thread) {
@@ -4210,6 +4428,9 @@ var UserManager = class {
4210
4428
  if (!user) {
4211
4429
  return false;
4212
4430
  }
4431
+ if (!user.wsIds || !Array.isArray(user.wsIds)) {
4432
+ user.wsIds = [];
4433
+ }
4213
4434
  if (!user.wsIds.includes(wsId)) {
4214
4435
  user.wsIds.push(wsId);
4215
4436
  this.hasChanged = true;
@@ -4228,6 +4449,9 @@ var UserManager = class {
4228
4449
  if (!user) {
4229
4450
  return false;
4230
4451
  }
4452
+ if (!user.wsIds || !Array.isArray(user.wsIds)) {
4453
+ return false;
4454
+ }
4231
4455
  const initialLength = user.wsIds.length;
4232
4456
  user.wsIds = user.wsIds.filter((id) => id !== wsId);
4233
4457
  if (user.wsIds.length < initialLength) {
@@ -4793,6 +5017,9 @@ var SuperatomSDK = class {
4793
5017
  this.userManager = new UserManager(this.projectId, 5e3);
4794
5018
  this.dashboardManager = new DashboardManager(this.projectId);
4795
5019
  this.reportManager = new ReportManager(this.projectId);
5020
+ this.initializePromptLoader(config.promptsDir).catch((error) => {
5021
+ logger.error("Failed to initialize PromptLoader:", error);
5022
+ });
4796
5023
  this.initializeUserManager().catch((error) => {
4797
5024
  logger.error("Failed to initialize UserManager:", error);
4798
5025
  });
@@ -4802,6 +5029,21 @@ var SuperatomSDK = class {
4802
5029
  logger.error("Failed to connect to Superatom:", error);
4803
5030
  });
4804
5031
  }
5032
+ /**
5033
+ * Initialize PromptLoader and load prompts into memory
5034
+ */
5035
+ async initializePromptLoader(promptsDir) {
5036
+ try {
5037
+ if (promptsDir) {
5038
+ promptLoader.setPromptsDir(promptsDir);
5039
+ }
5040
+ await promptLoader.initialize();
5041
+ logger.info(`PromptLoader initialized with ${promptLoader.getCacheSize()} prompts from ${promptLoader.getPromptsDir()}`);
5042
+ } catch (error) {
5043
+ logger.error("Failed to initialize PromptLoader:", error);
5044
+ throw error;
5045
+ }
5046
+ }
4805
5047
  /**
4806
5048
  * Initialize UserManager for the project
4807
5049
  */