@superatomai/sdk-node 0.0.13 → 0.0.14
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 +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +1325 -1477
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1325 -1477
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -562,6 +562,25 @@ var ReportsRequestMessageSchema = import_zod3.z.object({
|
|
|
562
562
|
type: import_zod3.z.literal("REPORTS"),
|
|
563
563
|
payload: ReportsRequestPayloadSchema
|
|
564
564
|
});
|
|
565
|
+
var UIBlockSchema = import_zod3.z.object({
|
|
566
|
+
id: import_zod3.z.string().optional(),
|
|
567
|
+
userQuestion: import_zod3.z.string().optional(),
|
|
568
|
+
text: import_zod3.z.string().optional(),
|
|
569
|
+
textResponse: import_zod3.z.string().optional(),
|
|
570
|
+
component: ComponentSchema.optional(),
|
|
571
|
+
// Legacy field
|
|
572
|
+
generatedComponentMetadata: ComponentSchema.optional(),
|
|
573
|
+
// Actual field used by UIBlock class
|
|
574
|
+
componentData: import_zod3.z.record(import_zod3.z.any()).optional(),
|
|
575
|
+
actions: import_zod3.z.array(import_zod3.z.any()).optional(),
|
|
576
|
+
isFetchingActions: import_zod3.z.boolean().optional(),
|
|
577
|
+
createdAt: import_zod3.z.string().optional(),
|
|
578
|
+
metadata: import_zod3.z.object({
|
|
579
|
+
timestamp: import_zod3.z.number().optional(),
|
|
580
|
+
userPrompt: import_zod3.z.string().optional(),
|
|
581
|
+
similarity: import_zod3.z.number().optional()
|
|
582
|
+
}).optional()
|
|
583
|
+
});
|
|
565
584
|
var BookmarkDataSchema = import_zod3.z.object({
|
|
566
585
|
id: import_zod3.z.number().optional(),
|
|
567
586
|
uiblock: import_zod3.z.any(),
|
|
@@ -1829,489 +1848,126 @@ var import_path2 = __toESM(require("path"));
|
|
|
1829
1848
|
|
|
1830
1849
|
// src/userResponse/prompts.ts
|
|
1831
1850
|
var PROMPTS = {
|
|
1832
|
-
"
|
|
1833
|
-
system: `You are an
|
|
1834
|
-
|
|
1835
|
-
CRITICAL: You MUST respond with ONLY valid JSON, no other text before or after.
|
|
1836
|
-
|
|
1837
|
-
## Previous Conversation
|
|
1838
|
-
{{CONVERSATION_HISTORY}}
|
|
1839
|
-
|
|
1840
|
-
**Note:** If there is previous conversation history, use it to understand context. For example:
|
|
1841
|
-
- If user previously asked about "sales" and now asks "show me trends", understand it refers to sales trends
|
|
1842
|
-
- If user asked for "revenue by region" and now says "make it a pie chart", understand they want to modify the previous visualization
|
|
1843
|
-
- Use the history to resolve ambiguous references like "that", "it", "them", "the data"
|
|
1844
|
-
|
|
1845
|
-
Your task is to analyze the user's question and determine:
|
|
1846
|
-
|
|
1847
|
-
1. **Question Type:**
|
|
1848
|
-
- "analytical": Questions asking to VIEW, ANALYZE, or VISUALIZE data
|
|
1849
|
-
|
|
1850
|
-
- "data_modification": Questions asking to CREATE, UPDATE, DELETE, or MODIFY data
|
|
1851
|
-
|
|
1852
|
-
- "general": General questions, greetings, or requests not related to data
|
|
1853
|
-
|
|
1854
|
-
2. **Required Visualizations** (only for analytical questions):
|
|
1855
|
-
Determine which visualization type(s) would BEST answer the user's question:
|
|
1856
|
-
|
|
1857
|
-
- **KPICard**: Single metric, total, count, average, percentage, or summary number
|
|
1858
|
-
|
|
1859
|
-
- **LineChart**: Trends over time, time series, growth/decline patterns
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
- **BarChart**: Comparing categories, rankings, distributions across groups
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
- **PieChart**: Proportions, percentages, composition, market share
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
- **DataTable**: Detailed lists, multiple attributes, when user needs to see records
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
3. **Multiple Visualizations:**
|
|
1872
|
-
User may need MULTIPLE visualizations together:
|
|
1873
|
-
|
|
1874
|
-
Common combinations:
|
|
1875
|
-
- KPICard + LineChart
|
|
1876
|
-
- KPICard + BarChart
|
|
1877
|
-
- KPICard + DataTable
|
|
1878
|
-
- BarChart + PieChart:
|
|
1879
|
-
- LineChart + DataTable
|
|
1880
|
-
Set needsMultipleComponents to true if user needs multiple views of the data.
|
|
1851
|
+
"text-response": {
|
|
1852
|
+
system: `You are an intelligent AI assistant that provides helpful, accurate, and contextual text responses to user questions. You have access to a database and can execute SQL queries and external tools to answer user requests.
|
|
1881
1853
|
|
|
1882
|
-
|
|
1883
|
-
- If user explicitly mentions a chart type RESPECT that preference
|
|
1884
|
-
- If question is vague or needs both summary and detail, suggest KPICard + DataTable
|
|
1885
|
-
- Only return visualizations for "analytical" questions
|
|
1886
|
-
- For "data_modification" or "general", return empty array for visualizations
|
|
1854
|
+
## Your Task
|
|
1887
1855
|
|
|
1888
|
-
|
|
1889
|
-
{
|
|
1890
|
-
"questionType": "analytical" | "data_modification" | "general",
|
|
1891
|
-
"visualizations": ["KPICard", "LineChart", ...], // Empty array if not analytical
|
|
1892
|
-
"reasoning": "Explanation of classification and visualization choices",
|
|
1893
|
-
"needsMultipleComponents": boolean
|
|
1894
|
-
}
|
|
1895
|
-
`,
|
|
1896
|
-
user: `{{USER_PROMPT}}
|
|
1897
|
-
`
|
|
1898
|
-
},
|
|
1899
|
-
"match-component": {
|
|
1900
|
-
system: `You are an expert AI assistant specialized in matching user requests to the most appropriate data visualization components.
|
|
1856
|
+
Analyze the user's question and provide a helpful text response. Your response should:
|
|
1901
1857
|
|
|
1902
|
-
|
|
1858
|
+
1. **Be Clear and Concise**: Provide direct answers without unnecessary verbosity
|
|
1859
|
+
2. **Be Contextual**: Use conversation history to understand what the user is asking about
|
|
1860
|
+
3. **Be Accurate**: Provide factually correct information based on the context
|
|
1861
|
+
4. **Be Helpful**: Offer additional relevant information or suggestions when appropriate
|
|
1903
1862
|
|
|
1904
|
-
##
|
|
1905
|
-
{{CONVERSATION_HISTORY}}
|
|
1863
|
+
## Available Tools
|
|
1906
1864
|
|
|
1907
|
-
|
|
1908
|
-
- If there is conversation history, use it to understand what the user is referring to
|
|
1909
|
-
- When user says "show that as a chart" or "change it", they are referring to a previous component
|
|
1910
|
-
- If user asks to "modify" or "update" something, match to the component they previously saw
|
|
1911
|
-
- Use context to resolve ambiguous requests like "show trends for that" or "make it interactive"
|
|
1912
|
-
|
|
1913
|
-
Your task is to analyze the user's natural language request and find the BEST matching component from the available list.
|
|
1914
|
-
|
|
1915
|
-
Available Components:
|
|
1916
|
-
{{COMPONENTS_TEXT}}
|
|
1917
|
-
|
|
1918
|
-
**Matching Guidelines:**
|
|
1919
|
-
|
|
1920
|
-
1. **Understand User Intent:**
|
|
1921
|
-
- What type of data visualization do they need? (KPI/metric, chart, table, etc.)
|
|
1922
|
-
- What metric or data are they asking about? (revenue, orders, customers, etc.)
|
|
1923
|
-
- Are they asking for a summary (KPI), trend (line chart), distribution (bar/pie), or detailed list (table)?
|
|
1924
|
-
- Do they want to compare categories, see trends over time, or show proportions?
|
|
1925
|
-
|
|
1926
|
-
2. **Component Type Matching:**
|
|
1927
|
-
- KPICard: Single metric/number (total, average, count, percentage, rate)
|
|
1928
|
-
- LineChart: Trends over time, time series data
|
|
1929
|
-
- BarChart: Comparing categories, distributions, rankings
|
|
1930
|
-
- PieChart/DonutChart: Proportions, percentages, market share
|
|
1931
|
-
- DataTable: Detailed lists, rankings with multiple attributes
|
|
1932
|
-
|
|
1933
|
-
3. **Keyword & Semantic Matching:**
|
|
1934
|
-
- Match user query terms with component keywords
|
|
1935
|
-
- Consider synonyms (e.g., "sales" = "revenue", "items" = "products")
|
|
1936
|
-
- Look for category matches (financial, orders, customers, products, suppliers, logistics, geographic, operations)
|
|
1937
|
-
|
|
1938
|
-
4. **Scoring Criteria:**
|
|
1939
|
-
- Exact keyword matches: High priority
|
|
1940
|
-
- Component type alignment: High priority
|
|
1941
|
-
- Category alignment: Medium priority
|
|
1942
|
-
- Semantic similarity: Medium priority
|
|
1943
|
-
- Specificity: Prefer more specific components over generic ones
|
|
1944
|
-
|
|
1945
|
-
**Output Requirements:**
|
|
1946
|
-
|
|
1947
|
-
Respond with a JSON object containing:
|
|
1948
|
-
- componentIndex: the 1-based index of the BEST matching component (or null if confidence < 50%)
|
|
1949
|
-
- componentId: the ID of the matched component
|
|
1950
|
-
- reasoning: detailed explanation of why this component was chosen
|
|
1951
|
-
- confidence: confidence score 0-100 (100 = perfect match)
|
|
1952
|
-
- alternativeMatches: array of up to 2 alternative component indices with scores (optional)
|
|
1953
|
-
|
|
1954
|
-
Example response:
|
|
1955
|
-
{
|
|
1956
|
-
"componentIndex": 5,
|
|
1957
|
-
"componentId": "total_revenue_kpi",
|
|
1958
|
-
"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'.",
|
|
1959
|
-
"confidence": 95,
|
|
1960
|
-
"alternativeMatches": [
|
|
1961
|
-
{"index": 3, "id": "monthly_revenue_kpi", "score": 75, "reason": "Could show monthly revenue if time period was intended"},
|
|
1962
|
-
{"index": 8, "id": "revenue_trend_chart", "score": 60, "reason": "Could show revenue trend if historical view was intended"}
|
|
1963
|
-
]
|
|
1964
|
-
}
|
|
1865
|
+
The following external tools are available for this request (if applicable):
|
|
1965
1866
|
|
|
1966
|
-
|
|
1967
|
-
- Only return componentIndex if confidence >= 50%
|
|
1968
|
-
- Return null if no reasonable match exists
|
|
1969
|
-
- Prefer components that exactly match the user's metric over generic ones
|
|
1970
|
-
- Consider the full context of the request, not just individual words`,
|
|
1971
|
-
user: `Current user request: {{USER_PROMPT}}
|
|
1867
|
+
{{AVAILABLE_EXTERNAL_TOOLS}}
|
|
1972
1868
|
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1869
|
+
When a tool is needed to complete the user's request:
|
|
1870
|
+
1. **Analyze the request** to determine which tool(s) are needed
|
|
1871
|
+
2. **Extract parameters** from the user's question that the tool requires
|
|
1872
|
+
3. **Execute the tool** by calling it with the extracted parameters
|
|
1873
|
+
4. **Present the results** in your response in a clear, user-friendly format
|
|
1874
|
+
5. **Combine with other data** if the user's request requires both database queries and external tool results
|
|
1977
1875
|
|
|
1978
|
-
|
|
1876
|
+
## Handling Data Questions
|
|
1979
1877
|
|
|
1980
|
-
|
|
1981
|
-
- A user's natural language request
|
|
1982
|
-
- Component name: {{COMPONENT_NAME}}
|
|
1983
|
-
- Component type: {{COMPONENT_TYPE}}
|
|
1984
|
-
- Component description: {{COMPONENT_DESCRIPTION}}
|
|
1878
|
+
When the user asks about data
|
|
1985
1879
|
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
title?: string, // Component title
|
|
1991
|
-
description?: string, // Component description
|
|
1992
|
-
config?: { // Additional configuration
|
|
1993
|
-
[key: string]: any
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1880
|
+
1. **Generate a SQL query** using the database schema provided above
|
|
1881
|
+
2. **Use the execute_query tool** to run the query
|
|
1882
|
+
3. **If the query fails**, analyze the error and generate a corrected query
|
|
1883
|
+
4. **Format the results** in a clear, readable way for the user
|
|
1996
1884
|
|
|
1997
|
-
|
|
1998
|
-
|
|
1885
|
+
**Query Guidelines:**
|
|
1886
|
+
- Use correct table and column names from the schema
|
|
1887
|
+
- ALWAYS include a LIMIT clause with a MAXIMUM of 32 rows
|
|
1888
|
+
- Ensure valid SQL syntax
|
|
1889
|
+
- For time-based queries, use appropriate date functions
|
|
1890
|
+
- When using subqueries with scalar operators (=, <, >, etc.), add LIMIT 1 to prevent "more than one row" errors
|
|
1999
1891
|
|
|
2000
|
-
Database Schema
|
|
1892
|
+
## Database Schema
|
|
2001
1893
|
{{SCHEMA_DOC}}
|
|
2002
1894
|
|
|
2003
|
-
|
|
2004
|
-
{{CONVERSATION_HISTORY}}
|
|
2005
|
-
|
|
2006
|
-
**Context Instructions:**
|
|
2007
|
-
- Review the conversation history to understand the evolution of the component
|
|
2008
|
-
- If user says "add filter for X", understand they want to modify the current query
|
|
2009
|
-
- If user says "change to last month" or "filter by Y", apply modifications to existing query
|
|
2010
|
-
- Previous questions can clarify what the user means by ambiguous requests like "change that filter"
|
|
2011
|
-
- Use context to determine appropriate time ranges if user says "recent" or "latest"
|
|
2012
|
-
|
|
2013
|
-
Your task is to intelligently modify the props based on the user's request:
|
|
1895
|
+
**Database Type: PostgreSQL**
|
|
2014
1896
|
|
|
2015
|
-
|
|
2016
|
-
- Modify SQL query if user requests different data, filters, time ranges, limits, or aggregations
|
|
2017
|
-
- Use correct table and column names from the schema
|
|
2018
|
-
- Ensure valid SQL syntax
|
|
2019
|
-
- ALWAYS include a LIMIT clause (default: {{DEFAULT_LIMIT}} rows) to prevent large result sets
|
|
2020
|
-
- Preserve the query structure that the component expects (e.g., column aliases)
|
|
1897
|
+
**CRITICAL PostgreSQL Query Rules:**
|
|
2021
1898
|
|
|
2022
|
-
|
|
1899
|
+
1. **NO AGGREGATE FUNCTIONS IN WHERE CLAUSE** - This is a fundamental SQL error
|
|
1900
|
+
\u274C WRONG: \`WHERE COUNT(orders) > 0\`
|
|
1901
|
+
\u274C WRONG: \`WHERE SUM(price) > 100\`
|
|
1902
|
+
\u274C WRONG: \`WHERE AVG(rating) > 4.5\`
|
|
1903
|
+
\u274C WRONG: \`WHERE FLOOR(AVG(rating)) = 4\` (aggregate inside any function is still not allowed)
|
|
1904
|
+
\u274C WRONG: \`WHERE ROUND(SUM(price), 2) > 100\`
|
|
2023
1905
|
|
|
2024
|
-
**NO AGGREGATE FUNCTIONS IN WHERE CLAUSE:**
|
|
2025
|
-
\u274C WRONG: \`WHERE COUNT(orders) > 0\` or \`WHERE SUM(price) > 100\`
|
|
2026
1906
|
\u2705 CORRECT: Use HAVING (with GROUP BY), EXISTS, or subquery
|
|
1907
|
+
\u2705 CORRECT: Move aggregate logic to HAVING: \`GROUP BY ... HAVING FLOOR(AVG(rating)) = 4\`
|
|
1908
|
+
\u2705 CORRECT: Use subquery for filtering: \`WHERE product_id IN (SELECT product_id FROM ... GROUP BY ... HAVING AVG(rating) >= 4)\`
|
|
2027
1909
|
|
|
2028
|
-
|
|
2029
|
-
**WHERE vs HAVING:**
|
|
1910
|
+
2. **WHERE vs HAVING**
|
|
2030
1911
|
- WHERE filters rows BEFORE grouping (cannot use aggregates)
|
|
2031
1912
|
- HAVING filters groups AFTER grouping (can use aggregates)
|
|
2032
1913
|
- If using HAVING, you MUST have GROUP BY
|
|
2033
1914
|
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
- Example: \`WHERE location_id = (SELECT store_id FROM orders ORDER BY total_amount DESC LIMIT 1)\`
|
|
2038
|
-
- For multiple values, use \`IN\` instead: \`WHERE location_id IN (SELECT store_id FROM orders)\`
|
|
2039
|
-
- Test your subqueries mentally: if they could return multiple rows, add LIMIT 1 or use IN
|
|
2040
|
-
|
|
2041
|
-
2. **Title Modification**:
|
|
2042
|
-
- Update title to reflect the user's specific request
|
|
2043
|
-
- Keep it concise and descriptive
|
|
2044
|
-
- Match the tone of the original title
|
|
2045
|
-
|
|
2046
|
-
3. **Description Modification**:
|
|
2047
|
-
- Update description to explain what data is shown
|
|
2048
|
-
- Be specific about filters, time ranges, or groupings applied
|
|
2049
|
-
|
|
2050
|
-
4. **Config Modification** (based on component type):
|
|
2051
|
-
- For KPICard: formatter, gradient, icon
|
|
2052
|
-
- For Charts: colors, height, xKey, yKey, nameKey, valueKey
|
|
2053
|
-
- For Tables: columns, pageSize, formatters
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
Respond with a JSON object:
|
|
2057
|
-
{
|
|
2058
|
-
"props": { /* modified props object with query, title, description, config */ },
|
|
2059
|
-
"isModified": boolean,
|
|
2060
|
-
"reasoning": "brief explanation of changes",
|
|
2061
|
-
"modifications": ["list of specific changes made"]
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
IMPORTANT:
|
|
2065
|
-
- Return the COMPLETE props object, not just modified fields
|
|
2066
|
-
- Preserve the structure expected by the component type
|
|
2067
|
-
- Ensure query returns columns with expected aliases
|
|
2068
|
-
- Keep config properties that aren't affected by the request`,
|
|
2069
|
-
user: `{{USER_PROMPT}}`
|
|
2070
|
-
},
|
|
2071
|
-
"single-component": {
|
|
2072
|
-
system: `You are an expert AI assistant specialized in matching user requests to the most appropriate component from a filtered list.
|
|
2073
|
-
|
|
2074
|
-
CRITICAL: You MUST respond with ONLY valid JSON, no other text before or after.
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
## Previous Conversation
|
|
2078
|
-
{{CONVERSATION_HISTORY}}
|
|
2079
|
-
|
|
2080
|
-
**Context Instructions:**
|
|
2081
|
-
- If there is previous conversation history, use it to understand what the user is referring to
|
|
2082
|
-
- When user says "show trends", "add filters", "change that", understand they may be building on previous queries
|
|
2083
|
-
- Use previous component types and queries as context to inform your current matching
|
|
2084
|
-
|
|
2085
|
-
## Available Components (Type: {{COMPONENT_TYPE}})
|
|
2086
|
-
The following components have been filtered by type {{COMPONENT_TYPE}}. Select the BEST matching one:
|
|
2087
|
-
|
|
2088
|
-
{{COMPONENTS_LIST}}
|
|
2089
|
-
|
|
2090
|
-
{{VISUALIZATION_CONSTRAINT}}
|
|
2091
|
-
|
|
2092
|
-
**Select the BEST matching component** from the available {{COMPONENT_TYPE}} components listed above that would best answer the user's question.
|
|
2093
|
-
|
|
2094
|
-
**Matching Guidelines:**
|
|
2095
|
-
1. **Semantic Matching:**
|
|
2096
|
-
- Match based on component name, description, and keywords
|
|
2097
|
-
- Consider what metrics/data the user is asking about
|
|
2098
|
-
- Look for semantic similarity (e.g., "sales" matches "revenue", "orders" matches "purchases")
|
|
2099
|
-
|
|
2100
|
-
2. **Query Relevance:**
|
|
2101
|
-
- Consider the component's existing query structure
|
|
2102
|
-
- Does it query the right tables/columns for the user's question?
|
|
2103
|
-
- Can it be modified to answer the user's specific question?
|
|
2104
|
-
|
|
2105
|
-
3. **Scoring Criteria:**
|
|
2106
|
-
- Exact keyword matches in name/description: High priority
|
|
2107
|
-
- Semantic similarity to user intent: High priority
|
|
2108
|
-
- Appropriate aggregation/grouping: Medium priority
|
|
2109
|
-
- Category alignment: Medium priority
|
|
2110
|
-
|
|
2111
|
-
**Output Requirements:**
|
|
2112
|
-
|
|
2113
|
-
Respond with a JSON object:
|
|
2114
|
-
{
|
|
2115
|
-
"componentId": "matched_component_id",
|
|
2116
|
-
"componentIndex": 1, // 1-based index from the filtered list above
|
|
2117
|
-
"reasoning": "Detailed explanation of why this component best matches the user's question",
|
|
2118
|
-
"confidence": 85, // Confidence score 0-100
|
|
2119
|
-
"canGenerate": true // false if no suitable component found (confidence < 50)
|
|
2120
|
-
}
|
|
2121
|
-
|
|
2122
|
-
**Important:**
|
|
2123
|
-
- Only set canGenerate to true if confidence >= 50%
|
|
2124
|
-
- If no component from the list matches well (all have low relevance), set canGenerate to false
|
|
2125
|
-
- Consider the full context of the request and conversation history
|
|
2126
|
-
- The component's props (query, title, description, config) will be modified later based on the user's specific request
|
|
2127
|
-
- Focus on finding the component that is closest to what the user needs, even if it needs modification`,
|
|
2128
|
-
user: `{{USER_PROMPT}}
|
|
2129
|
-
|
|
2130
|
-
`
|
|
2131
|
-
},
|
|
2132
|
-
"mutli-component": {
|
|
2133
|
-
system: `You are an expert data analyst AI that creates comprehensive multi-component analytical dashboards with aesthetically pleasing and balanced layouts.
|
|
2134
|
-
|
|
2135
|
-
CRITICAL: You MUST respond with ONLY valid JSON, no other text before or after.
|
|
2136
|
-
|
|
2137
|
-
Database Schema:
|
|
2138
|
-
{{SCHEMA_DOC}}
|
|
2139
|
-
|
|
2140
|
-
## Previous Conversation
|
|
2141
|
-
{{CONVERSATION_HISTORY}}
|
|
2142
|
-
|
|
2143
|
-
**Context Instructions:**
|
|
2144
|
-
- Review the conversation history to understand what the user has asked before
|
|
2145
|
-
- If user is building on previous insights (e.g., "now show me X and Y"), use context to inform dashboard design
|
|
2146
|
-
- Previous queries can help determine appropriate filters, date ranges, or categories to use
|
|
2147
|
-
- If user asks for "comprehensive view" or "dashboard for X", include complementary components based on context
|
|
2148
|
-
|
|
2149
|
-
Given a user's analytical question and the required visualization types, your task is to:
|
|
2150
|
-
|
|
2151
|
-
1. **Determine Container Metadata:**
|
|
2152
|
-
- title: Clear, descriptive title for the entire dashboard (2-5 words)
|
|
2153
|
-
- description: Brief explanation of what insights this dashboard provides (1-2 sentences)
|
|
2154
|
-
|
|
2155
|
-
2. **Generate Props for Each Component:**
|
|
2156
|
-
For each visualization type requested, create tailored props:
|
|
2157
|
-
|
|
2158
|
-
- **query**: SQL query specific to this visualization using the database schema
|
|
2159
|
-
* Use correct table and column names
|
|
2160
|
-
* **DO NOT USE TOP keyword - use LIMIT instead (e.g., LIMIT 20, not TOP 20)**
|
|
2161
|
-
* ALWAYS include LIMIT clause ONCE at the end (default: {{DEFAULT_LIMIT}})
|
|
2162
|
-
* For KPICard: Return single row with column alias "value"
|
|
2163
|
-
* For Charts: Return appropriate columns (name/label and value, or x and y)
|
|
2164
|
-
* For Table: Return relevant columns
|
|
2165
|
-
|
|
2166
|
-
- **title**: Specific title for this component (2-4 words)
|
|
2167
|
-
|
|
2168
|
-
- **description**: What this specific component shows (1 sentence)
|
|
2169
|
-
|
|
2170
|
-
- **config**: Type-specific configuration
|
|
2171
|
-
* KPICard: { gradient, formatter, icon }
|
|
2172
|
-
* BarChart: { xKey, yKey, colors, height }
|
|
2173
|
-
* LineChart: { xKey, yKeys, colors, height }
|
|
2174
|
-
* PieChart: { nameKey, valueKey, colors, height }
|
|
2175
|
-
* DataTable: { pageSize }
|
|
2176
|
-
|
|
2177
|
-
3. **CRITICAL: Component Hierarchy and Ordering:**
|
|
2178
|
-
The ORDER of components in the array MUST follow this STRICT hierarchy for proper visual layout:
|
|
2179
|
-
|
|
2180
|
-
**HIERARCHY RULES (MUST FOLLOW IN THIS ORDER):**
|
|
2181
|
-
1. KPICards - ALWAYS FIRST (top of dashboard for summary metrics)
|
|
2182
|
-
2. Charts/Graphs - AFTER KPICards (middle of dashboard for visualizations)
|
|
2183
|
-
* BarChart, LineChart, PieChart, DonutChart
|
|
2184
|
-
3. DataTable - ALWAYS LAST (bottom of dashboard, full width for detailed data)
|
|
2185
|
-
|
|
2186
|
-
**LAYOUT BEHAVIOR (Frontend enforces):**
|
|
2187
|
-
- KPICards: Display in responsive grid (3 columns)
|
|
2188
|
-
- Single Chart (if only 1 chart): Takes FULL WIDTH
|
|
2189
|
-
- Multiple Charts (if 2+ charts): Display in 2-column grid
|
|
2190
|
-
- DataTable (if present): Always spans FULL WIDTH at bottom
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
**ABSOLUTELY DO NOT deviate from this hierarchy. Always place:**
|
|
2194
|
-
- KPICards first
|
|
2195
|
-
- Charts/Graphs second
|
|
2196
|
-
- DataTable last (if present)
|
|
2197
|
-
|
|
2198
|
-
**Important Guidelines:**
|
|
2199
|
-
- Each component should answer a DIFFERENT aspect of the user's question
|
|
2200
|
-
- Queries should be complementary, not duplicated
|
|
2201
|
-
- If user asks "Show total revenue and trend", generate:
|
|
2202
|
-
* KPICard: Single total value (FIRST)
|
|
2203
|
-
* LineChart: Revenue over time (SECOND)
|
|
2204
|
-
- Ensure queries use valid columns from the schema
|
|
2205
|
-
- Make titles descriptive and specific to what each component shows
|
|
2206
|
-
- **Snowflake Syntax MUST be used:**
|
|
2207
|
-
* Use LIMIT (not TOP)
|
|
2208
|
-
* Use DATE_TRUNC, DATEDIFF (not DATEPART)
|
|
2209
|
-
* Include LIMIT only ONCE per query at the end
|
|
2210
|
-
|
|
2211
|
-
**Output Format:**
|
|
2212
|
-
{
|
|
2213
|
-
"containerTitle": "Dashboard Title",
|
|
2214
|
-
"containerDescription": "Brief description of the dashboard insights",
|
|
2215
|
-
"components": [
|
|
2216
|
-
{
|
|
2217
|
-
"componentType": "KPICard" | "BarChart" | "LineChart" | "PieChart" | "DataTable",
|
|
2218
|
-
"query": "SQL query",
|
|
2219
|
-
"title": "Component title",
|
|
2220
|
-
"description": "Component description",
|
|
2221
|
-
"config": { /* type-specific config */ }
|
|
2222
|
-
},
|
|
2223
|
-
...
|
|
2224
|
-
],
|
|
2225
|
-
"reasoning": "Explanation of the dashboard design and component ordering",
|
|
2226
|
-
"canGenerate": boolean
|
|
2227
|
-
}`,
|
|
2228
|
-
user: `Current user question: {{USER_PROMPT}}
|
|
2229
|
-
|
|
2230
|
-
Required visualization types: {{VISUALIZATION_TYPES}}
|
|
2231
|
-
|
|
2232
|
-
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.`
|
|
2233
|
-
},
|
|
2234
|
-
"container-metadata": {
|
|
2235
|
-
system: `You are an expert AI assistant that generates titles and descriptions for multi-component dashboards.
|
|
2236
|
-
|
|
2237
|
-
CRITICAL: You MUST respond with ONLY valid JSON, no other text before or after.
|
|
2238
|
-
|
|
2239
|
-
## Previous Conversation
|
|
2240
|
-
{{CONVERSATION_HISTORY}}
|
|
2241
|
-
|
|
2242
|
-
**Context Instructions:**
|
|
2243
|
-
- If there is previous conversation history, use it to understand what the user is referring to
|
|
2244
|
-
- Use context to create relevant titles and descriptions that align with the user's intent
|
|
2245
|
-
|
|
2246
|
-
Your task is to generate a concise title and description for a multi-component dashboard that will contain the following visualization types:
|
|
2247
|
-
{{VISUALIZATION_TYPES}}
|
|
2248
|
-
|
|
2249
|
-
**Guidelines:**
|
|
2250
|
-
|
|
2251
|
-
1. **Title:**
|
|
2252
|
-
- Should be clear and descriptive (3-8 words)
|
|
2253
|
-
- Should reflect what the user is asking about
|
|
2254
|
-
- Should NOT include "Dashboard" suffix (that will be added automatically)
|
|
2255
|
-
|
|
2256
|
-
2. **Description:**
|
|
2257
|
-
- Should be a brief summary (1-2 sentences)
|
|
2258
|
-
- Should explain what insights the dashboard provides
|
|
2259
|
-
|
|
2260
|
-
**Output Requirements:**
|
|
2261
|
-
|
|
2262
|
-
Respond with a JSON object:
|
|
2263
|
-
{
|
|
2264
|
-
"title": "Dashboard title without 'Dashboard' suffix",
|
|
2265
|
-
"description": "Brief description of what this dashboard shows"
|
|
2266
|
-
}
|
|
2267
|
-
|
|
2268
|
-
**Important:**
|
|
2269
|
-
- Keep the title concise and meaningful
|
|
2270
|
-
- Make the description informative but brief
|
|
2271
|
-
- Focus on what insights the user will gain
|
|
2272
|
-
`,
|
|
2273
|
-
user: `{{USER_PROMPT}}
|
|
2274
|
-
`
|
|
2275
|
-
},
|
|
2276
|
-
"text-response": {
|
|
2277
|
-
system: `You are an intelligent AI assistant that provides helpful, accurate, and contextual text responses to user questions.
|
|
2278
|
-
|
|
2279
|
-
## Your Task
|
|
2280
|
-
|
|
2281
|
-
Analyze the user's question and provide a helpful text response. Your response should:
|
|
1915
|
+
3. **NO NESTED AGGREGATE FUNCTIONS** - PostgreSQL does NOT allow aggregates inside aggregates
|
|
1916
|
+
\u274C WRONG: \`AVG(ROUND(AVG(column), 2))\` or \`SELECT AVG(SUM(price)) FROM ...\`
|
|
1917
|
+
\u2705 CORRECT: \`ROUND(AVG(column), 2)\`
|
|
2282
1918
|
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
4. **Be Helpful**: Offer additional relevant information or suggestions when appropriate
|
|
1919
|
+
4. **GROUP BY Requirements**
|
|
1920
|
+
- ALL non-aggregated columns in SELECT must be in GROUP BY
|
|
1921
|
+
- If you SELECT a column and don't aggregate it, add it to GROUP BY
|
|
2287
1922
|
|
|
2288
|
-
|
|
1923
|
+
5. **LIMIT Clause**
|
|
1924
|
+
- ALWAYS include LIMIT (max 32 rows)
|
|
1925
|
+
- For scalar subqueries in WHERE/HAVING, add LIMIT 1
|
|
2289
1926
|
|
|
2290
|
-
|
|
1927
|
+
6. **String Escaping** - PostgreSQL uses double single-quotes, NOT backslash
|
|
1928
|
+
\u274C WRONG: \`'Children\\'s furniture'\`
|
|
1929
|
+
\u2705 CORRECT: \`'Children''s furniture'\`
|
|
2291
1930
|
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
1931
|
+
7. **Always Use Table Aliases for Column References** - Prevent ambiguous column errors
|
|
1932
|
+
\u274C WRONG: \`SELECT product_id FROM products p JOIN product_variants pv ON p.product_id = pv.product_id\`
|
|
1933
|
+
\u2705 CORRECT: \`SELECT p.product_id FROM products p JOIN product_variants pv ON p.product_id = pv.product_id\`
|
|
1934
|
+
- Always prefix columns with table alias (e.g., \`p.product_id\`, \`c.name\`)
|
|
1935
|
+
- Especially critical in subqueries and joins where multiple tables share column names
|
|
2296
1936
|
|
|
2297
|
-
**Query Guidelines:**
|
|
2298
|
-
- Use correct table and column names from the schema
|
|
2299
|
-
- ALWAYS include a LIMIT clause with a MAXIMUM of 32 rows
|
|
2300
|
-
- Ensure valid SQL syntax
|
|
2301
|
-
- For time-based queries, use appropriate date functions
|
|
2302
|
-
- When using subqueries with scalar operators (=, <, >, etc.), add LIMIT 1 to prevent "more than one row" errors
|
|
2303
1937
|
|
|
2304
1938
|
## Response Guidelines
|
|
2305
1939
|
|
|
2306
|
-
- If the question is about data, use the execute_query tool to fetch data and present it
|
|
1940
|
+
- If the question is about viewing data, use the execute_query tool to fetch data and present it
|
|
1941
|
+
- If the question is about creating/updating/deleting data:
|
|
1942
|
+
1. Acknowledge that the system supports this via forms
|
|
1943
|
+
2. **CRITICAL:** Use the database schema to determine which fields are required based on \`nullable\` property
|
|
1944
|
+
3. **CRITICAL:** If the form will have select fields for foreign keys, you MUST fetch the options data using execute_query
|
|
1945
|
+
4. **CRITICAL FOR UPDATE/DELETE OPERATIONS:** If it's an update/edit/modify/delete question:
|
|
1946
|
+
- **NEVER update ID/primary key columns** (e.g., order_id, customer_id, product_id) - these are immutable identifiers
|
|
1947
|
+
- You MUST first fetch the CURRENT values of the record using a SELECT query
|
|
1948
|
+
- Identify the record (from user's question - e.g., "update order 123" or "delete order 123" means order_id = 123)
|
|
1949
|
+
- Execute: \`SELECT * FROM table_name WHERE id = <value> LIMIT 1\`
|
|
1950
|
+
- Present the current values in your response (e.g., "Current order status: Pending, payment method: Credit Card")
|
|
1951
|
+
- For DELETE: These values will be shown in a disabled form as confirmation before deletion
|
|
1952
|
+
- For UPDATE: These values will populate as default values for editing
|
|
1953
|
+
5. Present the options data in your response (e.g., "Available categories: Furniture (id: 1), Kitchen (id: 2), Decor (id: 3)")
|
|
1954
|
+
6. The form component will be generated automatically using this data
|
|
2307
1955
|
- If the question is general knowledge, provide a helpful conversational response
|
|
2308
1956
|
- If asking for clarification, provide options or ask specific follow-up questions
|
|
2309
1957
|
- If you don't have enough information, acknowledge it and ask for more details
|
|
2310
1958
|
- Keep responses focused and avoid going off-topic
|
|
2311
1959
|
|
|
1960
|
+
**Example for data modification with foreign keys:**
|
|
1961
|
+
User: "I want to create a new product"
|
|
1962
|
+
You should:
|
|
1963
|
+
1. Execute query: \`SELECT category_id, name FROM categories LIMIT 32\`
|
|
1964
|
+
2. Execute query: \`SELECT store_id, name FROM stores LIMIT 32\`
|
|
1965
|
+
3. Present: "I can help you create a new product. Available categories: Furniture (id: 1), Kitchen (id: 2)... Available stores: Store A (id: 10), Store B (id: 20)..."
|
|
1966
|
+
4. Suggest Form component
|
|
1967
|
+
|
|
2312
1968
|
## Component Suggestions
|
|
2313
1969
|
|
|
2314
|
-
After analyzing the
|
|
1970
|
+
After analyzing the user's question, you MUST suggest appropriate dashboard components. Use this format:
|
|
2315
1971
|
|
|
2316
1972
|
<DashboardComponents>
|
|
2317
1973
|
**Dashboard Components:**
|
|
@@ -2319,12 +1975,22 @@ Format: \`{number}.{component_type} : {clear reasoning}\`
|
|
|
2319
1975
|
|
|
2320
1976
|
|
|
2321
1977
|
**Rules for component suggestions:**
|
|
2322
|
-
1.
|
|
2323
|
-
2.
|
|
2324
|
-
3.
|
|
1978
|
+
1. If a conclusive answer can be provided based on user question, suggest that as the first component.
|
|
1979
|
+
2. ALways suggest context/supporting components that will give the user more information and allow them to explore further.
|
|
1980
|
+
3. If the question includes a time range, also explore time-based components for past time ranges.
|
|
1981
|
+
4. **For data viewing/analysis questions**: Suggest visualization components (KPICard, BarChart, LineChart, PieChart, DataTable, etc.).
|
|
1982
|
+
5. **For data modification questions** (create/add/update/delete):
|
|
1983
|
+
- Always suggest 1-2 context components first to provide relevant information (prefer KPICard for showing key metrics)
|
|
1984
|
+
- Then suggest \`Form\` component for the actual modification
|
|
1985
|
+
- Example: "1.KPICard : Show current order total and status" then "2.Form : To update order details"
|
|
1986
|
+
6. Analyze the query results structure and data type
|
|
1987
|
+
7. Each component suggestion must be on a new line
|
|
2325
1988
|
</DashboardComponents>
|
|
2326
1989
|
|
|
2327
|
-
IMPORTANT:
|
|
1990
|
+
IMPORTANT:
|
|
1991
|
+
- Always wrap component suggestions with <DashboardComponents> tags
|
|
1992
|
+
- For data viewing: Include at least one component suggestion when data is returned
|
|
1993
|
+
- For data modifications: Always suggest 1-2 context components before Form (e.g., "1.KPICard : Show current order value" then "2.Form : To update order status")
|
|
2328
1994
|
|
|
2329
1995
|
## Output Format
|
|
2330
1996
|
|
|
@@ -2339,37 +2005,22 @@ Respond with plain text that includes:
|
|
|
2339
2005
|
- Return ONLY plain text (no JSON, no markdown code blocks)
|
|
2340
2006
|
|
|
2341
2007
|
|
|
2342
|
-
You have access to a database and can execute SQL queries to answer data-related questions.
|
|
2343
|
-
## Database Schema
|
|
2344
|
-
{{SCHEMA_DOC}}
|
|
2345
|
-
|
|
2346
|
-
**Database Type: PostgreSQL**
|
|
2347
|
-
|
|
2348
|
-
**CRITICAL PostgreSQL Query Rules:**
|
|
2008
|
+
You have access to a database and can execute SQL queries to answer data-related questions. For data modifications, the system provides form-based interfaces.
|
|
2349
2009
|
|
|
2350
|
-
1. **NO AGGREGATE FUNCTIONS IN WHERE CLAUSE** - This is a fundamental SQL error
|
|
2351
|
-
\u274C WRONG: \`WHERE COUNT(orders) > 0\`
|
|
2352
|
-
\u274C WRONG: \`WHERE SUM(price) > 100\`
|
|
2353
|
-
\u274C WRONG: \`WHERE AVG(rating) > 4.5\`
|
|
2354
2010
|
|
|
2355
|
-
|
|
2011
|
+
## External Tool Results
|
|
2356
2012
|
|
|
2357
|
-
|
|
2358
|
-
- WHERE filters rows BEFORE grouping (cannot use aggregates)
|
|
2359
|
-
- HAVING filters groups AFTER grouping (can use aggregates)
|
|
2360
|
-
- If using HAVING, you MUST have GROUP BY
|
|
2013
|
+
The following external tools were executed for this request (if applicable):
|
|
2361
2014
|
|
|
2362
|
-
|
|
2363
|
-
\u274C WRONG: \`AVG(ROUND(AVG(column), 2))\` or \`SELECT AVG(SUM(price)) FROM ...\`
|
|
2364
|
-
\u2705 CORRECT: \`ROUND(AVG(column), 2)\`
|
|
2015
|
+
{{EXTERNAL_TOOL_CONTEXT}}
|
|
2365
2016
|
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2017
|
+
Use this external tool data to:
|
|
2018
|
+
- Provide information from external sources (emails, calendar, etc.)
|
|
2019
|
+
- Present the data in a user-friendly format
|
|
2020
|
+
- Combine external data with database queries when relevant
|
|
2021
|
+
- Reference specific results in your response
|
|
2369
2022
|
|
|
2370
|
-
|
|
2371
|
-
- ALWAYS include LIMIT (max 32 rows)
|
|
2372
|
-
- For scalar subqueries in WHERE/HAVING, add LIMIT 1
|
|
2023
|
+
**Note:** If external tools were not needed, this section will indicate "No external tools were used for this request."
|
|
2373
2024
|
|
|
2374
2025
|
|
|
2375
2026
|
## Knowledge Base Context
|
|
@@ -2392,10 +2043,8 @@ Use this knowledge base information to:
|
|
|
2392
2043
|
## Previous Conversation
|
|
2393
2044
|
{{CONVERSATION_HISTORY}}
|
|
2394
2045
|
|
|
2395
|
-
|
|
2396
2046
|
`,
|
|
2397
2047
|
user: `{{USER_PROMPT}}
|
|
2398
|
-
|
|
2399
2048
|
`
|
|
2400
2049
|
},
|
|
2401
2050
|
"match-text-components": {
|
|
@@ -2409,11 +2058,21 @@ You will receive a text response containing:
|
|
|
2409
2058
|
3. **Dashboard Components:** suggestions (1:component_type : reasoning format)
|
|
2410
2059
|
|
|
2411
2060
|
Your job is to:
|
|
2412
|
-
1. **
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2061
|
+
1. **FIRST: Generate a direct answer component** (if the user question can be answered with a single visualization)
|
|
2062
|
+
- Determine the BEST visualization type (KPICard, BarChart, DataTable, PieChart, LineChart, etc.) to directly answer the user's question
|
|
2063
|
+
- Select the matching component from the available components list
|
|
2064
|
+
- Generate complete props for this component (query, title, description, config)
|
|
2065
|
+
- This component will be placed in the \`answerComponent\` field
|
|
2066
|
+
- This component will be streamed to the frontend IMMEDIATELY for instant user feedback
|
|
2067
|
+
- **CRITICAL**: Generate this FIRST in your JSON response
|
|
2068
|
+
|
|
2069
|
+
2. **THEN: Parse ALL dashboard component suggestions** from the text response (format: 1:component_type : reasoning)
|
|
2070
|
+
3. **Match EACH suggestion with an actual component** from the available list
|
|
2071
|
+
4. **CRITICAL**: \`matchedComponents\` must include **ALL** dashboard components suggested in the text, INCLUDING the component you used as \`answerComponent\`
|
|
2072
|
+
- The answerComponent is shown first for quick feedback, but the full dashboard shows everything
|
|
2073
|
+
5. **Generate proper props** for each matched component to **visualize the analysis results** that were already fetched
|
|
2074
|
+
6. **Generate title and description** for the dashboard container
|
|
2075
|
+
7. **Generate intelligent follow-up questions (actions)** that the user might naturally ask next based on the data analysis
|
|
2417
2076
|
|
|
2418
2077
|
**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.
|
|
2419
2078
|
|
|
@@ -2448,7 +2107,8 @@ For each matched component, generate complete props:
|
|
|
2448
2107
|
|
|
2449
2108
|
**Option B: GENERATE a new query** (when necessary)
|
|
2450
2109
|
- Only generate new queries when you need DIFFERENT data
|
|
2451
|
-
- Use the database schema below to write valid SQL
|
|
2110
|
+
- For SELECT queries: Use the database schema below to write valid SQL
|
|
2111
|
+
- For mutations (INSERT/UPDATE/DELETE): Only if matching a Form component, generate mutation query with $fieldName placeholders
|
|
2452
2112
|
|
|
2453
2113
|
|
|
2454
2114
|
**Decision Logic:**
|
|
@@ -2466,8 +2126,12 @@ For each matched component, generate complete props:
|
|
|
2466
2126
|
\u274C WRONG: \`WHERE COUNT(orders) > 0\`
|
|
2467
2127
|
\u274C WRONG: \`WHERE SUM(price) > 100\`
|
|
2468
2128
|
\u274C WRONG: \`WHERE AVG(rating) > 4.5\`
|
|
2129
|
+
\u274C WRONG: \`WHERE FLOOR(AVG(rating)) = 4\` (aggregate inside any function is still not allowed)
|
|
2130
|
+
\u274C WRONG: \`WHERE ROUND(SUM(price), 2) > 100\`
|
|
2469
2131
|
|
|
2470
2132
|
\u2705 CORRECT: Use HAVING (with GROUP BY), EXISTS, or subquery
|
|
2133
|
+
\u2705 CORRECT: Move aggregate logic to HAVING: \`GROUP BY ... HAVING FLOOR(AVG(rating)) = 4\`
|
|
2134
|
+
\u2705 CORRECT: Use subquery for filtering: \`WHERE product_id IN (SELECT product_id FROM ... GROUP BY ... HAVING AVG(rating) >= 4)\`
|
|
2471
2135
|
|
|
2472
2136
|
2. **NO NESTED AGGREGATE FUNCTIONS** - PostgreSQL does NOT allow aggregates inside aggregates
|
|
2473
2137
|
\u274C WRONG: \`AVG(ROUND(AVG(column), 2))\`
|
|
@@ -2496,6 +2160,16 @@ For each matched component, generate complete props:
|
|
|
2496
2160
|
- Subqueries used with =, <, >, etc. must return single value
|
|
2497
2161
|
- Always add LIMIT 1 to scalar subqueries
|
|
2498
2162
|
|
|
2163
|
+
8. **String Escaping** - PostgreSQL uses double single-quotes, NOT backslash
|
|
2164
|
+
\u274C WRONG: \`'Children\\'s furniture'\`
|
|
2165
|
+
\u2705 CORRECT: \`'Children''s furniture'\`
|
|
2166
|
+
|
|
2167
|
+
9. **Always Use Table Aliases for Column References** - Prevent ambiguous column errors
|
|
2168
|
+
\u274C WRONG: \`SELECT product_id FROM products p JOIN product_variants pv ON p.product_id = pv.product_id\`
|
|
2169
|
+
\u2705 CORRECT: \`SELECT p.product_id FROM products p JOIN product_variants pv ON p.product_id = pv.product_id\`
|
|
2170
|
+
- Always prefix columns with table alias (e.g., \`p.product_id\`, \`c.name\`)
|
|
2171
|
+
- Especially critical in subqueries and joins where multiple tables share column names
|
|
2172
|
+
|
|
2499
2173
|
**Query Generation Guidelines** (when creating new queries):
|
|
2500
2174
|
- Use correct table and column names from the schema above
|
|
2501
2175
|
- ALWAYS include LIMIT clause (max 32 rows)
|
|
@@ -2509,7 +2183,7 @@ For each matched component, generate complete props:
|
|
|
2509
2183
|
- Brief explanation of what this component displays
|
|
2510
2184
|
- Why it's useful for this data
|
|
2511
2185
|
|
|
2512
|
-
### 4. Config
|
|
2186
|
+
### 4. Config (for visualization components)
|
|
2513
2187
|
- **CRITICAL**: Look at the component's "Props Structure" to see what config fields it expects
|
|
2514
2188
|
- Map query result columns to the appropriate config fields
|
|
2515
2189
|
- Keep other existing config properties that don't need to change
|
|
@@ -2521,40 +2195,167 @@ For each matched component, generate complete props:
|
|
|
2521
2195
|
- \`orientation\` = "vertical" or "horizontal" (controls visual direction only)
|
|
2522
2196
|
- **DO NOT swap xAxisKey/yAxisKey based on orientation** - they always represent category and value respectively
|
|
2523
2197
|
|
|
2524
|
-
|
|
2198
|
+
### 5. Additional Props (match according to component type)
|
|
2199
|
+
- **CRITICAL**: Look at the matched component's "Props Structure" in the available components list
|
|
2200
|
+
- Generate props that match EXACTLY what the component expects
|
|
2525
2201
|
|
|
2526
|
-
|
|
2202
|
+
**For Form components (type: "Form"):**
|
|
2527
2203
|
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2204
|
+
Props structure:
|
|
2205
|
+
- **query**: \`{ sql: "INSERT/UPDATE/DELETE query with $fieldName placeholders", params: [] }\`
|
|
2206
|
+
- **For UPDATE queries**: Check the database schema - if the table has an \`updated_at\` or \`last_updated\` column, always include it in the SET clause with \`CURRENT_TIMESTAMP\` (e.g., \`UPDATE table_name SET field = $field, updated_at = CURRENT_TIMESTAMP WHERE id = value\`)
|
|
2207
|
+
- **title**: "Update Order 5000", "Create New Product", or "Delete Order 5000"
|
|
2208
|
+
- **description**: What the form does
|
|
2209
|
+
- **submitButtonText**: Button text (default: "Submit"). For delete: "Delete", "Confirm Delete"
|
|
2210
|
+
- **submitButtonColor**: "primary" (blue) or "danger" (red). Use "danger" for DELETE operations
|
|
2211
|
+
- **successMessage**: Success message (default: "Form submitted successfully!"). For delete: "Record deleted successfully!"
|
|
2212
|
+
- **disableFields**: Set \`true\` for DELETE operations to show current values but prevent editing
|
|
2213
|
+
- **fields**: Array of field objects (structure below)
|
|
2535
2214
|
|
|
2215
|
+
**Field object:**
|
|
2216
|
+
\`\`\`json
|
|
2217
|
+
{
|
|
2218
|
+
"name": "field_name", // Matches $field_name in SQL query
|
|
2219
|
+
"description": "Field Label",
|
|
2220
|
+
"type": "text|number|email|date|select|multiselect|checkbox|textarea",
|
|
2221
|
+
"required": true, // Set based on schema: nullable=false \u2192 required=true, nullable=true \u2192 required=false
|
|
2222
|
+
"defaultValue": "current_value", // For UPDATE: extract from text response
|
|
2223
|
+
"placeholder": "hint text",
|
|
2224
|
+
"options": [...], // For select/multiselect
|
|
2225
|
+
"validation": {
|
|
2226
|
+
"minLength": { "value": 5, "message": "..." },
|
|
2227
|
+
"maxLength": { "value": 100, "message": "..." },
|
|
2228
|
+
"min": { "value": 18, "message": "..." },
|
|
2229
|
+
"max": { "value": 120, "message": "..." },
|
|
2230
|
+
"pattern": { "value": "regex", "message": "..." }
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
\`\`\`
|
|
2536
2234
|
|
|
2537
|
-
|
|
2235
|
+
**CRITICAL - Set required based on database schema:**
|
|
2236
|
+
- Check the column's \`nullable\` property in the database schema
|
|
2237
|
+
- If \`nullable: false\` \u2192 set \`required: true\` (field is mandatory)
|
|
2238
|
+
- If \`nullable: true\` \u2192 set \`required: false\` (field is optional)
|
|
2239
|
+
- Never set fields as required if the schema allows NULL
|
|
2538
2240
|
|
|
2539
|
-
|
|
2241
|
+
**Default Values for UPDATE:**
|
|
2242
|
+
- **NEVER include ID/primary key fields in UPDATE forms** (e.g., order_id, customer_id, product_id) - these cannot be changed
|
|
2243
|
+
- Detect UPDATE by checking if SQL contains "UPDATE" keyword
|
|
2244
|
+
- Extract current values from text response (look for "Current values:" or SELECT results)
|
|
2245
|
+
- Set \`defaultValue\` for each field with the extracted current value
|
|
2540
2246
|
|
|
2541
|
-
**
|
|
2542
|
-
|
|
2543
|
-
- Escape all quotes in SQL properly (use \\" for quotes inside strings)
|
|
2544
|
-
- Remove any newlines, tabs, or special characters from SQL
|
|
2545
|
-
- Do NOT use markdown code blocks (no \`\`\`)
|
|
2546
|
-
- Return ONLY the JSON object, nothing else
|
|
2247
|
+
**CRITICAL - Single field with current value pre-selected:**
|
|
2248
|
+
For UPDATE operations, use ONE field with defaultValue set to current value (not two separate fields).
|
|
2547
2249
|
|
|
2250
|
+
\u2705 CORRECT - Single field, current value pre-selected:
|
|
2548
2251
|
\`\`\`json
|
|
2549
2252
|
{
|
|
2550
|
-
"
|
|
2551
|
-
"
|
|
2552
|
-
"
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2253
|
+
"name": "category_id",
|
|
2254
|
+
"type": "select",
|
|
2255
|
+
"defaultValue": 5,
|
|
2256
|
+
"options": [{"id": 1, "name": "Kitchen"}, {"id": 5, "name": "Furniture"}, {"id": 7, "name": "Decor"}]
|
|
2257
|
+
}
|
|
2258
|
+
\`\`\`
|
|
2259
|
+
User sees dropdown with "Furniture" selected, can change to any other category.
|
|
2260
|
+
|
|
2261
|
+
\u274C WRONG - Two separate fields:
|
|
2262
|
+
\`\`\`json
|
|
2263
|
+
[
|
|
2264
|
+
{"name": "current_category", "type": "text", "defaultValue": "Furniture", "disabled": true},
|
|
2265
|
+
{"name": "new_category", "type": "select", "options": [...]}
|
|
2266
|
+
]
|
|
2267
|
+
\`\`\`
|
|
2268
|
+
|
|
2269
|
+
**Options Format:**
|
|
2270
|
+
- **Enum/status fields** (non-foreign keys): String array \`["Pending", "Shipped", "Delivered"]\`
|
|
2271
|
+
- **Foreign keys** (reference tables): Object array \`[{"id": 1, "name": "Furniture"}, {"id": 2, "name": "Kitchen"}]\`
|
|
2272
|
+
- Extract from text response queries and match format to field type
|
|
2273
|
+
|
|
2274
|
+
**Example UPDATE form field:**
|
|
2275
|
+
\`\`\`json
|
|
2276
|
+
{
|
|
2277
|
+
"name": "status",
|
|
2278
|
+
"description": "Order Status",
|
|
2279
|
+
"type": "select",
|
|
2280
|
+
"required": true,
|
|
2281
|
+
"defaultValue": "Pending", // Current value from database
|
|
2282
|
+
"options": ["Pending", "Processing", "Shipped", "Delivered"]
|
|
2283
|
+
}
|
|
2284
|
+
\`\`\`
|
|
2285
|
+
|
|
2286
|
+
**Example DELETE form props:**
|
|
2287
|
+
\`\`\`json
|
|
2288
|
+
{
|
|
2289
|
+
"query": { "sql": "DELETE FROM orders WHERE order_id = 123", "params": [] },
|
|
2290
|
+
"title": "Delete Order 123",
|
|
2291
|
+
"description": "Are you sure you want to delete this order?",
|
|
2292
|
+
"submitButtonText": "Confirm Delete",
|
|
2293
|
+
"submitButtonColor": "danger",
|
|
2294
|
+
"successMessage": "Order deleted successfully!",
|
|
2295
|
+
"disableFields": true,
|
|
2296
|
+
"fields": [
|
|
2297
|
+
{ "name": "order_id", "description": "Order ID", "type": "text", "defaultValue": "123" },
|
|
2298
|
+
{ "name": "status", "description": "Status", "type": "text", "defaultValue": "Pending" }
|
|
2299
|
+
]
|
|
2300
|
+
}
|
|
2301
|
+
\`\`\`
|
|
2302
|
+
|
|
2303
|
+
**For visualization components (Charts, Tables, KPIs):**
|
|
2304
|
+
- **query**: String (SQL SELECT query)
|
|
2305
|
+
- **title**, **description**, **config**: As per component's props structure
|
|
2306
|
+
- Do NOT include fields array
|
|
2307
|
+
|
|
2308
|
+
## Follow-Up Questions (Actions) Generation
|
|
2309
|
+
|
|
2310
|
+
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:
|
|
2311
|
+
|
|
2312
|
+
1. **Build upon the data analysis** shown in the text response and components
|
|
2313
|
+
2. **Explore natural next steps** in the data exploration journey
|
|
2314
|
+
3. **Be progressively more detailed or specific** - go deeper into the analysis
|
|
2315
|
+
4. **Consider the insights revealed** - suggest questions that help users understand implications
|
|
2316
|
+
5. **Be phrased naturally** as if a real user would ask them
|
|
2317
|
+
6. **Vary in scope** - include both broad trends and specific details
|
|
2318
|
+
7. **Avoid redundancy** - don't ask questions already answered in the text response
|
|
2319
|
+
|
|
2320
|
+
|
|
2321
|
+
## Output Format
|
|
2322
|
+
|
|
2323
|
+
You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
|
|
2324
|
+
|
|
2325
|
+
**IMPORTANT JSON FORMATTING RULES:**
|
|
2326
|
+
- Put SQL queries on a SINGLE LINE (no newlines in the query string)
|
|
2327
|
+
- Escape all quotes in SQL properly (use \\" for quotes inside strings)
|
|
2328
|
+
- Remove any newlines, tabs, or special characters from SQL
|
|
2329
|
+
- Do NOT use markdown code blocks (no \`\`\`)
|
|
2330
|
+
- Return ONLY the JSON object, nothing else
|
|
2331
|
+
|
|
2332
|
+
**Example 1: With answer component** (when user question can be answered with single visualization)
|
|
2333
|
+
\`\`\`json
|
|
2334
|
+
{
|
|
2335
|
+
"hasAnswerComponent": true,
|
|
2336
|
+
"answerComponent": {
|
|
2337
|
+
"componentId": "id_from_available_list",
|
|
2338
|
+
"componentName": "name_of_component",
|
|
2339
|
+
"componentType": "type_of_component (can be KPICard, BarChart, LineChart, PieChart, DataTable, etc.)",
|
|
2340
|
+
"reasoning": "Why this visualization type best answers the user's question",
|
|
2341
|
+
"props": {
|
|
2342
|
+
"query": "SQL query for this component",
|
|
2343
|
+
"title": "Component title that directly answers the user's question",
|
|
2344
|
+
"description": "Component description",
|
|
2345
|
+
"config": {
|
|
2346
|
+
"field1": "value1",
|
|
2347
|
+
"field2": "value2"
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
},
|
|
2351
|
+
"layoutTitle": "Clear, concise title for the overall dashboard/layout (5-10 words)",
|
|
2352
|
+
"layoutDescription": "Brief description of what the dashboard shows and its purpose (1-2 sentences)",
|
|
2353
|
+
"matchedComponents": [
|
|
2354
|
+
{
|
|
2355
|
+
"componentId": "id_from_available_list",
|
|
2356
|
+
"componentName": "name_of_component",
|
|
2357
|
+
"componentType": "type_of_component",
|
|
2358
|
+
"reasoning": "Why this component was selected for the dashboard",
|
|
2558
2359
|
"originalSuggestion": "c1:table : original reasoning from text",
|
|
2559
2360
|
"props": {
|
|
2560
2361
|
"query": "SQL query for this component",
|
|
@@ -2577,21 +2378,65 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code blocks):
|
|
|
2577
2378
|
}
|
|
2578
2379
|
\`\`\`
|
|
2579
2380
|
|
|
2381
|
+
**Example 2: Without answer component** (when user question needs multiple visualizations or dashboard)
|
|
2382
|
+
\`\`\`json
|
|
2383
|
+
{
|
|
2384
|
+
"hasAnswerComponent": false,
|
|
2385
|
+
"answerComponent": null,
|
|
2386
|
+
"layoutTitle": "Clear, concise title for the overall dashboard/layout (5-10 words)",
|
|
2387
|
+
"layoutDescription": "Brief description of what the dashboard shows and its purpose (1-2 sentences)",
|
|
2388
|
+
"matchedComponents": [
|
|
2389
|
+
{
|
|
2390
|
+
"componentId": "id_from_available_list",
|
|
2391
|
+
"componentName": "name_of_component",
|
|
2392
|
+
"componentType": "type_of_component",
|
|
2393
|
+
"reasoning": "Why this component was selected for the dashboard",
|
|
2394
|
+
"originalSuggestion": "c1:chart : original reasoning from text",
|
|
2395
|
+
"props": {
|
|
2396
|
+
"query": "SQL query for this component",
|
|
2397
|
+
"title": "Component title",
|
|
2398
|
+
"description": "Component description",
|
|
2399
|
+
"config": {
|
|
2400
|
+
"field1": "value1",
|
|
2401
|
+
"field2": "value2"
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
],
|
|
2406
|
+
"actions": [
|
|
2407
|
+
"Follow-up question 1?",
|
|
2408
|
+
"Follow-up question 2?",
|
|
2409
|
+
"Follow-up question 3?",
|
|
2410
|
+
"Follow-up question 4?",
|
|
2411
|
+
"Follow-up question 5?"
|
|
2412
|
+
]
|
|
2413
|
+
}
|
|
2414
|
+
\`\`\`
|
|
2415
|
+
|
|
2580
2416
|
**CRITICAL:**
|
|
2581
|
-
- \`
|
|
2417
|
+
- **\`hasAnswerComponent\` determines if an answer component exists**
|
|
2418
|
+
- Set to \`true\` if the user question can be answered with a single visualization
|
|
2419
|
+
- Set to \`false\` if the user question can not be answered with single visualisation and needs multiple visualizations or a dashboard overview
|
|
2420
|
+
- **If \`hasAnswerComponent\` is \`true\`:**
|
|
2421
|
+
- \`answerComponent\` MUST be generated FIRST in the JSON before \`layoutTitle\`
|
|
2422
|
+
- Generate complete props (query, title, description, config)
|
|
2423
|
+
- **If \`hasAnswerComponent\` is \`false\`:**
|
|
2424
|
+
- Set \`answerComponent\` to \`null\`
|
|
2425
|
+
- **\`matchedComponents\` MUST include ALL dashboard components from the text analysis**
|
|
2426
|
+
- **CRITICAL**: Even if you used a component as \`answerComponent\`, you MUST STILL include it in \`matchedComponents\`
|
|
2427
|
+
- The count of matchedComponents should EQUAL the count of dashboard suggestions in the text (e.g., if text has 4 suggestions, matchedComponents should have 4 items)
|
|
2428
|
+
- Do NOT skip the answerComponent from matchedComponents
|
|
2429
|
+
- \`matchedComponents\` come from the dashboard component suggestions in the text response
|
|
2582
2430
|
- \`layoutTitle\` MUST be a clear, concise title (5-10 words) that summarizes what the entire dashboard shows
|
|
2583
|
-
- Examples: "Sales Performance Overview", "Customer Metrics Analysis", "Product Category Breakdown"
|
|
2584
2431
|
- \`layoutDescription\` MUST be a brief description (1-2 sentences) explaining the purpose and scope of the dashboard
|
|
2585
2432
|
- Should describe what insights the dashboard provides and what data it shows
|
|
2586
2433
|
- \`actions\` MUST be an array of 4-5 intelligent follow-up questions based on the analysis
|
|
2587
2434
|
- Return ONLY valid JSON (no markdown code blocks, no text before/after)
|
|
2588
|
-
- Generate complete props for each component
|
|
2589
|
-
|
|
2590
|
-
|
|
2435
|
+
- Generate complete props for each component
|
|
2591
2436
|
`,
|
|
2592
|
-
user: `##
|
|
2437
|
+
user: `## Analysis Content
|
|
2593
2438
|
|
|
2594
|
-
{{
|
|
2439
|
+
{{ANALYSIS_CONTENT}}
|
|
2595
2440
|
|
|
2596
2441
|
---
|
|
2597
2442
|
|
|
@@ -2638,70 +2483,269 @@ Format your response as a JSON object with this structure:
|
|
|
2638
2483
|
|
|
2639
2484
|
Return ONLY valid JSON.`
|
|
2640
2485
|
},
|
|
2641
|
-
"
|
|
2642
|
-
system: `You are an expert AI
|
|
2486
|
+
"category-classification": {
|
|
2487
|
+
system: `You are an expert AI that categorizes user questions into specific action categories and identifies required tools/resources.
|
|
2643
2488
|
|
|
2644
|
-
You
|
|
2489
|
+
CRITICAL: You MUST respond with ONLY valid JSON, no other text before or after.
|
|
2645
2490
|
|
|
2646
2491
|
## Available External Tools
|
|
2492
|
+
|
|
2647
2493
|
{{AVAILABLE_TOOLS}}
|
|
2648
2494
|
|
|
2649
|
-
|
|
2495
|
+
---
|
|
2650
2496
|
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
1. **
|
|
2654
|
-
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2497
|
+
Your task is to analyze the user's question and determine:
|
|
2498
|
+
|
|
2499
|
+
1. **Question Category:**
|
|
2500
|
+
- "data_analysis": Questions about analyzing, querying, reading, or visualizing data from the database (SELECT operations)
|
|
2501
|
+
- "data_modification": Questions about creating, updating, deleting, or modifying data in the database (INSERT, UPDATE, DELETE operations)
|
|
2502
|
+
|
|
2503
|
+
2. **External Tools Required** (for both categories):
|
|
2504
|
+
From the available tools listed above, identify which ones are needed to support the user's request:
|
|
2505
|
+
- Match the tool names/descriptions to what the user is asking for
|
|
2506
|
+
- Extract specific parameters mentioned in the user's question
|
|
2507
|
+
|
|
2508
|
+
3. **Tool Parameters** (if tools are identified):
|
|
2509
|
+
Extract specific parameters the user mentioned:
|
|
2510
|
+
- For each identified tool, extract relevant parameters (email, recipient, content, etc.)
|
|
2511
|
+
- Only include parameters the user explicitly or implicitly mentioned
|
|
2512
|
+
|
|
2513
|
+
**Important Guidelines:**
|
|
2514
|
+
- If user mentions any of the available external tools \u2192 identify those tools and extract their parameters
|
|
2515
|
+
- If user asks to "send", "schedule", "create event", "message" \u2192 check if available tools match
|
|
2516
|
+
- If user asks to "show", "analyze", "compare", "calculate" data \u2192 "data_analysis"
|
|
2517
|
+
- If user asks to modify/create/update/delete data \u2192 "data_modification"
|
|
2518
|
+
- Always identify tools from the available tools list (not from generic descriptions)
|
|
2519
|
+
- Be precise in identifying tool types and required parameters
|
|
2520
|
+
- Only include tools that are explicitly mentioned or clearly needed
|
|
2521
|
+
|
|
2522
|
+
**Output Format:**
|
|
2523
|
+
\`\`\`json
|
|
2524
|
+
{
|
|
2525
|
+
"category": "data_analysis" | "data_modification",
|
|
2526
|
+
"reasoning": "Brief explanation of why this category was chosen",
|
|
2527
|
+
"externalTools": [
|
|
2528
|
+
{
|
|
2529
|
+
"type": "tool_id_from_available_tools",
|
|
2530
|
+
"name": "Tool Display Name",
|
|
2531
|
+
"description": "What this tool will do",
|
|
2532
|
+
"parameters": {
|
|
2533
|
+
"param1": "extracted value",
|
|
2534
|
+
"param2": "extracted value"
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
],
|
|
2538
|
+
"dataAnalysisType": "visualization" | "calculation" | "comparison" | "trend" | null,
|
|
2539
|
+
"confidence": 0-100
|
|
2540
|
+
}
|
|
2541
|
+
\`\`\`
|
|
2542
|
+
|
|
2543
|
+
|
|
2544
|
+
## Previous Conversation
|
|
2545
|
+
{{CONVERSATION_HISTORY}}`,
|
|
2546
|
+
user: `{{USER_PROMPT}}`
|
|
2547
|
+
},
|
|
2548
|
+
"adapt-ui-block-params": {
|
|
2549
|
+
system: `You are an expert AI that adapts and modifies UI block component parameters based on the user's current question.
|
|
2550
|
+
|
|
2551
|
+
CRITICAL: You MUST respond with ONLY valid JSON, no other text before or after.
|
|
2552
|
+
|
|
2553
|
+
## Database Schema Reference
|
|
2554
|
+
|
|
2555
|
+
{{SCHEMA_DOC}}
|
|
2556
|
+
|
|
2557
|
+
Use this schema to understand available tables, columns, and relationships when modifying SQL queries. Ensure all table and column names you use in adapted queries are valid according to this schema.
|
|
2558
|
+
|
|
2559
|
+
## Context
|
|
2560
|
+
You are given:
|
|
2561
|
+
1. A previous UI Block response (with component and its props) that matched the user's current question with >90% semantic similarity
|
|
2562
|
+
2. The user's current question
|
|
2563
|
+
3. The component that needs parameter adaptation
|
|
2564
|
+
|
|
2565
|
+
Your task is to:
|
|
2566
|
+
1. **Analyze the difference** between the original question (from the matched UIBlock) and the current user question
|
|
2567
|
+
2. **Identify what parameters need to change** in the component props to answer the current question
|
|
2568
|
+
3. **Modify the props** to match the current request while keeping the same component type(s)
|
|
2569
|
+
4. **Preserve component structure** - only change props, not the components themselves
|
|
2570
|
+
|
|
2571
|
+
## Component Structure Handling
|
|
2572
|
+
|
|
2573
|
+
### For Single Components:
|
|
2574
|
+
- Modify props directly (config, actions, query, filters, etc.)
|
|
2575
|
+
|
|
2576
|
+
### For MultiComponentContainer:
|
|
2577
|
+
The component will have structure:
|
|
2578
|
+
\`\`\`json
|
|
2579
|
+
{
|
|
2580
|
+
"type": "Container",
|
|
2581
|
+
"name": "MultiComponentContainer",
|
|
2582
|
+
"props": {
|
|
2583
|
+
"config": {
|
|
2584
|
+
"components": [...], // Array of nested components - ADAPT EACH ONE
|
|
2585
|
+
"title": "...", // Container title - UPDATE based on new question
|
|
2586
|
+
"description": "..." // Container description - UPDATE based on new question
|
|
2587
|
+
},
|
|
2588
|
+
"actions": [...] // ADAPT actions if needed
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
\`\`\`
|
|
2592
|
+
|
|
2593
|
+
When adapting MultiComponentContainer:
|
|
2594
|
+
- Update the container-level \`title\` and \`description\` to reflect the new user question
|
|
2595
|
+
- For each component in \`config.components\`:
|
|
2596
|
+
- Identify what data it shows and how the new question changes what's needed
|
|
2597
|
+
- Adapt its query parameters (WHERE clauses, LIMIT, ORDER BY, filters, date ranges)
|
|
2598
|
+
- Update its title/description to match the new context
|
|
2599
|
+
- Update its config settings (colors, sorting, grouping, metrics)
|
|
2600
|
+
- Update \`actions\` if the new question requires different actions
|
|
2601
|
+
|
|
2602
|
+
## Important Guidelines:
|
|
2603
|
+
- Keep the same component type (don't change KPICard to LineChart)
|
|
2604
|
+
- Keep the same number of components in the container
|
|
2605
|
+
- For each nested component, update:
|
|
2606
|
+
- Query WHERE clauses, LIMIT, ORDER BY, filters, date ranges, metrics
|
|
2607
|
+
- Title and description to reflect the new question
|
|
2608
|
+
- Config settings like colors, sorting, grouping if needed
|
|
2609
|
+
- Maintain each component's core purpose while answering the new question
|
|
2610
|
+
- If query modification is needed, ensure all table/column names remain valid
|
|
2611
|
+
- CRITICAL: Ensure JSON is valid and complete for all nested structures
|
|
2612
|
+
|
|
2613
|
+
|
|
2614
|
+
## Output Format:
|
|
2615
|
+
|
|
2616
|
+
### For Single Component:
|
|
2617
|
+
\`\`\`json
|
|
2618
|
+
{
|
|
2619
|
+
"success": true,
|
|
2620
|
+
"adaptedComponent": {
|
|
2621
|
+
"id": "original_component_id",
|
|
2622
|
+
"name": "component_name",
|
|
2623
|
+
"type": "component_type",
|
|
2624
|
+
"description": "updated_description",
|
|
2625
|
+
"props": {
|
|
2626
|
+
"config": { },
|
|
2627
|
+
"actions": [],
|
|
2628
|
+
}
|
|
2629
|
+
},
|
|
2630
|
+
"parametersChanged": [
|
|
2631
|
+
{
|
|
2632
|
+
"field": "query",
|
|
2633
|
+
"reason": "Added Q4 date filter"
|
|
2634
|
+
},
|
|
2635
|
+
{
|
|
2636
|
+
"field": "title",
|
|
2637
|
+
"reason": "Updated to reflect Q4 focus"
|
|
2638
|
+
}
|
|
2639
|
+
],
|
|
2640
|
+
"explanation": "How the component was adapted to answer the new question"
|
|
2641
|
+
}
|
|
2642
|
+
\`\`\`
|
|
2643
|
+
|
|
2644
|
+
### For MultiComponentContainer:
|
|
2645
|
+
\`\`\`json
|
|
2646
|
+
{
|
|
2647
|
+
"success": true,
|
|
2648
|
+
"adaptedComponent": {
|
|
2649
|
+
"id": "original_container_id",
|
|
2650
|
+
"name": "MultiComponentContainer",
|
|
2651
|
+
"type": "Container",
|
|
2652
|
+
"description": "updated_container_description",
|
|
2653
|
+
"props": {
|
|
2654
|
+
"config": {
|
|
2655
|
+
"title": "Updated dashboard title based on new question",
|
|
2656
|
+
"description": "Updated description reflecting new question context",
|
|
2657
|
+
"components": [
|
|
2658
|
+
{
|
|
2659
|
+
"id": "component_1_id",
|
|
2660
|
+
"name": "component_1_name",
|
|
2661
|
+
"type": "component_1_type",
|
|
2662
|
+
"description": "updated description for this specific component",
|
|
2663
|
+
"props": {
|
|
2664
|
+
"query": "Modified SQL query with updated WHERE/LIMIT/ORDER BY",
|
|
2665
|
+
"config": { "metric": "updated_metric", "filters": {...} }
|
|
2666
|
+
}
|
|
2667
|
+
},
|
|
2668
|
+
{
|
|
2669
|
+
"id": "component_2_id",
|
|
2670
|
+
"name": "component_2_name",
|
|
2671
|
+
"type": "component_2_type",
|
|
2672
|
+
"description": "updated description for this component",
|
|
2673
|
+
"props": {
|
|
2674
|
+
"query": "Modified SQL query for this component",
|
|
2675
|
+
"config": { "metric": "updated_metric", "filters": {...} }
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
]
|
|
2679
|
+
},
|
|
2680
|
+
"actions": []
|
|
2681
|
+
}
|
|
2682
|
+
},
|
|
2683
|
+
"parametersChanged": [
|
|
2684
|
+
{
|
|
2685
|
+
"field": "container.title",
|
|
2686
|
+
"reason": "Updated to reflect new dashboard focus"
|
|
2687
|
+
},
|
|
2688
|
+
{
|
|
2689
|
+
"field": "components[0].query",
|
|
2690
|
+
"reason": "Modified WHERE clause for new metrics"
|
|
2691
|
+
},
|
|
2692
|
+
{
|
|
2693
|
+
"field": "components[1].config.metric",
|
|
2694
|
+
"reason": "Changed metric from X to Y based on new question"
|
|
2695
|
+
}
|
|
2696
|
+
],
|
|
2697
|
+
"explanation": "Detailed explanation of how each component was adapted"
|
|
2698
|
+
}
|
|
2699
|
+
\`\`\`
|
|
2700
|
+
|
|
2701
|
+
If adaptation is not possible or would fundamentally change the component:
|
|
2702
|
+
\`\`\`json
|
|
2703
|
+
{
|
|
2704
|
+
"success": false,
|
|
2705
|
+
"reason": "Cannot adapt component - the new question requires a different visualization type",
|
|
2706
|
+
"explanation": "The original component shows KPI cards but the new question needs a trend chart"
|
|
2707
|
+
}
|
|
2708
|
+
\`\`\``,
|
|
2709
|
+
user: `## Previous Matched UIBlock
|
|
2710
|
+
|
|
2711
|
+
**Original Question:** {{ORIGINAL_USER_PROMPT}}
|
|
2712
|
+
|
|
2713
|
+
**Matched UIBlock Component:**
|
|
2714
|
+
\`\`\`json
|
|
2715
|
+
{{MATCHED_UI_BLOCK_COMPONENT}}
|
|
2716
|
+
\`\`\`
|
|
2717
|
+
|
|
2718
|
+
**Component Properties:**
|
|
2719
|
+
\`\`\`json
|
|
2720
|
+
{{COMPONENT_PROPS}}
|
|
2721
|
+
\`\`\`
|
|
2722
|
+
|
|
2723
|
+
## Current User Question
|
|
2724
|
+
{{CURRENT_USER_PROMPT}}
|
|
2725
|
+
|
|
2726
|
+
---
|
|
2727
|
+
|
|
2728
|
+
## Adaptation Instructions
|
|
2729
|
+
|
|
2730
|
+
1. **Analyze the difference** between the original question and the current question
|
|
2731
|
+
2. **Identify what data needs to change**:
|
|
2732
|
+
- For single components: adapt the query/config/actions
|
|
2733
|
+
- For MultiComponentContainer: adapt the container title/description AND each nested component's parameters
|
|
2734
|
+
|
|
2735
|
+
3. **Modify the parameters**:
|
|
2736
|
+
- **Container level** (if MultiComponentContainer):
|
|
2737
|
+
- Update \`title\` and \`description\` to reflect the new user question
|
|
2738
|
+
- Update \`actions\` if needed
|
|
2739
|
+
|
|
2740
|
+
- **For each component** (single or nested in container):
|
|
2741
|
+
- Identify what it shows (sales, revenue, inventory, etc.)
|
|
2742
|
+
- Adapt SQL queries: modify WHERE clauses, LIMIT, ORDER BY, filters, date ranges
|
|
2743
|
+
- Update component title and description
|
|
2744
|
+
- Update config settings (metrics, colors, sorting, grouping)
|
|
2745
|
+
|
|
2746
|
+
4. **Preserve structure**: Keep the same number and type of components
|
|
2747
|
+
|
|
2748
|
+
5. **Return complete JSON** with all adapted properties for all components`
|
|
2705
2749
|
}
|
|
2706
2750
|
};
|
|
2707
2751
|
|
|
@@ -2779,9 +2823,10 @@ var PromptLoader = class {
|
|
|
2779
2823
|
}
|
|
2780
2824
|
/**
|
|
2781
2825
|
* Load both system and user prompts from cache and replace variables
|
|
2826
|
+
* Supports prompt caching by splitting static and dynamic content
|
|
2782
2827
|
* @param promptName - Name of the prompt
|
|
2783
2828
|
* @param variables - Variables to replace in the templates
|
|
2784
|
-
* @returns Object containing both system and user prompts
|
|
2829
|
+
* @returns Object containing both system and user prompts (system can be string or array for caching)
|
|
2785
2830
|
*/
|
|
2786
2831
|
async loadPrompts(promptName, variables) {
|
|
2787
2832
|
if (!this.isInitialized) {
|
|
@@ -2792,6 +2837,26 @@ var PromptLoader = class {
|
|
|
2792
2837
|
if (!template) {
|
|
2793
2838
|
throw new Error(`Prompt template '${promptName}' not found in cache. Available prompts: ${Array.from(this.promptCache.keys()).join(", ")}`);
|
|
2794
2839
|
}
|
|
2840
|
+
const contextMarker = "---\n\n## CONTEXT";
|
|
2841
|
+
if (template.system.includes(contextMarker)) {
|
|
2842
|
+
const [staticPart, contextPart] = template.system.split(contextMarker);
|
|
2843
|
+
logger.debug(`\u2713 Prompt caching enabled for '${promptName}' (static: ${staticPart.length} chars, context: ${contextPart.length} chars)`);
|
|
2844
|
+
const processedContext = this.replaceVariables(contextMarker + contextPart, variables);
|
|
2845
|
+
return {
|
|
2846
|
+
system: [
|
|
2847
|
+
{
|
|
2848
|
+
type: "text",
|
|
2849
|
+
text: staticPart.trim(),
|
|
2850
|
+
cache_control: { type: "ephemeral" }
|
|
2851
|
+
},
|
|
2852
|
+
{
|
|
2853
|
+
type: "text",
|
|
2854
|
+
text: processedContext.trim()
|
|
2855
|
+
}
|
|
2856
|
+
],
|
|
2857
|
+
user: this.replaceVariables(template.user, variables)
|
|
2858
|
+
};
|
|
2859
|
+
}
|
|
2795
2860
|
return {
|
|
2796
2861
|
system: this.replaceVariables(template.system, variables),
|
|
2797
2862
|
user: this.replaceVariables(template.user, variables)
|
|
@@ -2878,6 +2943,75 @@ var LLM = class {
|
|
|
2878
2943
|
// ============================================================
|
|
2879
2944
|
// PRIVATE HELPER METHODS
|
|
2880
2945
|
// ============================================================
|
|
2946
|
+
/**
|
|
2947
|
+
* Normalize system prompt to Anthropic format
|
|
2948
|
+
* Converts string to array format if needed
|
|
2949
|
+
* @param sys - System prompt (string or array of blocks)
|
|
2950
|
+
* @returns Normalized system prompt for Anthropic API
|
|
2951
|
+
*/
|
|
2952
|
+
static _normalizeSystemPrompt(sys) {
|
|
2953
|
+
if (typeof sys === "string") {
|
|
2954
|
+
return sys;
|
|
2955
|
+
}
|
|
2956
|
+
return sys;
|
|
2957
|
+
}
|
|
2958
|
+
/**
|
|
2959
|
+
* Log cache usage metrics from Anthropic API response
|
|
2960
|
+
* Shows cache hits, costs, and savings
|
|
2961
|
+
*/
|
|
2962
|
+
static _logCacheUsage(usage) {
|
|
2963
|
+
if (!usage) return;
|
|
2964
|
+
const inputTokens = usage.input_tokens || 0;
|
|
2965
|
+
const cacheCreationTokens = usage.cache_creation_input_tokens || 0;
|
|
2966
|
+
const cacheReadTokens = usage.cache_read_input_tokens || 0;
|
|
2967
|
+
const outputTokens = usage.output_tokens || 0;
|
|
2968
|
+
const INPUT_PRICE = 0.8;
|
|
2969
|
+
const OUTPUT_PRICE = 4;
|
|
2970
|
+
const CACHE_WRITE_PRICE = 1;
|
|
2971
|
+
const CACHE_READ_PRICE = 0.08;
|
|
2972
|
+
const regularInputCost = inputTokens / 1e6 * INPUT_PRICE;
|
|
2973
|
+
const cacheWriteCost = cacheCreationTokens / 1e6 * CACHE_WRITE_PRICE;
|
|
2974
|
+
const cacheReadCost = cacheReadTokens / 1e6 * CACHE_READ_PRICE;
|
|
2975
|
+
const outputCost = outputTokens / 1e6 * OUTPUT_PRICE;
|
|
2976
|
+
const totalCost = regularInputCost + cacheWriteCost + cacheReadCost + outputCost;
|
|
2977
|
+
const totalInputTokens = inputTokens + cacheCreationTokens + cacheReadTokens;
|
|
2978
|
+
const costWithoutCache = totalInputTokens / 1e6 * INPUT_PRICE + outputCost;
|
|
2979
|
+
const savings = costWithoutCache - totalCost;
|
|
2980
|
+
const savingsPercent = costWithoutCache > 0 ? savings / costWithoutCache * 100 : 0;
|
|
2981
|
+
console.log("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
2982
|
+
console.log("\u{1F4B0} PROMPT CACHING METRICS");
|
|
2983
|
+
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
2984
|
+
console.log("\n\u{1F4CA} Token Usage:");
|
|
2985
|
+
console.log(` Input (regular): ${inputTokens.toLocaleString()} tokens`);
|
|
2986
|
+
if (cacheCreationTokens > 0) {
|
|
2987
|
+
console.log(` Cache write: ${cacheCreationTokens.toLocaleString()} tokens (first request)`);
|
|
2988
|
+
}
|
|
2989
|
+
if (cacheReadTokens > 0) {
|
|
2990
|
+
console.log(` Cache read: ${cacheReadTokens.toLocaleString()} tokens \u26A1 HIT!`);
|
|
2991
|
+
}
|
|
2992
|
+
console.log(` Output: ${outputTokens.toLocaleString()} tokens`);
|
|
2993
|
+
console.log(` Total input: ${totalInputTokens.toLocaleString()} tokens`);
|
|
2994
|
+
console.log("\n\u{1F4B5} Cost Breakdown:");
|
|
2995
|
+
console.log(` Input (regular): $${regularInputCost.toFixed(6)}`);
|
|
2996
|
+
if (cacheCreationTokens > 0) {
|
|
2997
|
+
console.log(` Cache write: $${cacheWriteCost.toFixed(6)}`);
|
|
2998
|
+
}
|
|
2999
|
+
if (cacheReadTokens > 0) {
|
|
3000
|
+
console.log(` Cache read: $${cacheReadCost.toFixed(6)} (90% off!)`);
|
|
3001
|
+
}
|
|
3002
|
+
console.log(` Output: $${outputCost.toFixed(6)}`);
|
|
3003
|
+
console.log(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
3004
|
+
console.log(` Total cost: $${totalCost.toFixed(6)}`);
|
|
3005
|
+
if (cacheReadTokens > 0) {
|
|
3006
|
+
console.log(`
|
|
3007
|
+
\u{1F48E} Savings: $${savings.toFixed(6)} (${savingsPercent.toFixed(1)}% off)`);
|
|
3008
|
+
console.log(` Without cache: $${costWithoutCache.toFixed(6)}`);
|
|
3009
|
+
} else if (cacheCreationTokens > 0) {
|
|
3010
|
+
console.log(`
|
|
3011
|
+
\u23F1\uFE0F Cache created - next request will be ~90% cheaper!`);
|
|
3012
|
+
}
|
|
3013
|
+
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
|
|
3014
|
+
}
|
|
2881
3015
|
/**
|
|
2882
3016
|
* Parse model string to extract provider and model name
|
|
2883
3017
|
* @param modelString - Format: "provider/model-name" or just "model-name"
|
|
@@ -2912,7 +3046,7 @@ var LLM = class {
|
|
|
2912
3046
|
model: modelName,
|
|
2913
3047
|
max_tokens: options.maxTokens || 1e3,
|
|
2914
3048
|
temperature: options.temperature,
|
|
2915
|
-
system: messages.sys,
|
|
3049
|
+
system: this._normalizeSystemPrompt(messages.sys),
|
|
2916
3050
|
messages: [{
|
|
2917
3051
|
role: "user",
|
|
2918
3052
|
content: messages.user
|
|
@@ -2930,7 +3064,7 @@ var LLM = class {
|
|
|
2930
3064
|
model: modelName,
|
|
2931
3065
|
max_tokens: options.maxTokens || 1e3,
|
|
2932
3066
|
temperature: options.temperature,
|
|
2933
|
-
system: messages.sys,
|
|
3067
|
+
system: this._normalizeSystemPrompt(messages.sys),
|
|
2934
3068
|
messages: [{
|
|
2935
3069
|
role: "user",
|
|
2936
3070
|
content: messages.user
|
|
@@ -2938,6 +3072,7 @@ var LLM = class {
|
|
|
2938
3072
|
stream: true
|
|
2939
3073
|
});
|
|
2940
3074
|
let fullText = "";
|
|
3075
|
+
let usage = null;
|
|
2941
3076
|
for await (const chunk of stream) {
|
|
2942
3077
|
if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
|
|
2943
3078
|
const text = chunk.delta.text;
|
|
@@ -2945,8 +3080,12 @@ var LLM = class {
|
|
|
2945
3080
|
if (options.partial) {
|
|
2946
3081
|
options.partial(text);
|
|
2947
3082
|
}
|
|
3083
|
+
} else if (chunk.type === "message_delta" && chunk.usage) {
|
|
3084
|
+
usage = chunk.usage;
|
|
2948
3085
|
}
|
|
2949
3086
|
}
|
|
3087
|
+
if (usage) {
|
|
3088
|
+
}
|
|
2950
3089
|
if (json) {
|
|
2951
3090
|
return this._parseJSON(fullText);
|
|
2952
3091
|
}
|
|
@@ -2969,7 +3108,7 @@ var LLM = class {
|
|
|
2969
3108
|
model: modelName,
|
|
2970
3109
|
max_tokens: options.maxTokens || 4e3,
|
|
2971
3110
|
temperature: options.temperature,
|
|
2972
|
-
system: messages.sys,
|
|
3111
|
+
system: this._normalizeSystemPrompt(messages.sys),
|
|
2973
3112
|
messages: conversationMessages,
|
|
2974
3113
|
tools,
|
|
2975
3114
|
stream: true
|
|
@@ -2979,6 +3118,7 @@ var LLM = class {
|
|
|
2979
3118
|
const contentBlocks = [];
|
|
2980
3119
|
let currentTextBlock = "";
|
|
2981
3120
|
let currentToolUse = null;
|
|
3121
|
+
let usage = null;
|
|
2982
3122
|
for await (const chunk of stream) {
|
|
2983
3123
|
if (chunk.type === "message_start") {
|
|
2984
3124
|
contentBlocks.length = 0;
|
|
@@ -3029,11 +3169,16 @@ var LLM = class {
|
|
|
3029
3169
|
}
|
|
3030
3170
|
if (chunk.type === "message_delta") {
|
|
3031
3171
|
stopReason = chunk.delta.stop_reason || stopReason;
|
|
3172
|
+
if (chunk.usage) {
|
|
3173
|
+
usage = chunk.usage;
|
|
3174
|
+
}
|
|
3032
3175
|
}
|
|
3033
3176
|
if (chunk.type === "message_stop") {
|
|
3034
3177
|
break;
|
|
3035
3178
|
}
|
|
3036
3179
|
}
|
|
3180
|
+
if (usage) {
|
|
3181
|
+
}
|
|
3037
3182
|
if (stopReason === "end_turn") {
|
|
3038
3183
|
break;
|
|
3039
3184
|
}
|
|
@@ -3205,6 +3350,57 @@ var KB = {
|
|
|
3205
3350
|
};
|
|
3206
3351
|
var knowledge_base_default = KB;
|
|
3207
3352
|
|
|
3353
|
+
// src/userResponse/conversation-search.ts
|
|
3354
|
+
var searchConversations = async ({
|
|
3355
|
+
userPrompt,
|
|
3356
|
+
collections,
|
|
3357
|
+
userId,
|
|
3358
|
+
similarityThreshold = 0.6
|
|
3359
|
+
}) => {
|
|
3360
|
+
try {
|
|
3361
|
+
if (!collections || !collections["conversation-history"] || !collections["conversation-history"]["search"]) {
|
|
3362
|
+
logger.info("[ConversationSearch] conversation-history.search collection not registered, skipping");
|
|
3363
|
+
return null;
|
|
3364
|
+
}
|
|
3365
|
+
logger.info(`[ConversationSearch] Searching conversations for: "${userPrompt.substring(0, 50)}..."`);
|
|
3366
|
+
logger.info(`[ConversationSearch] Using similarity threshold: ${(similarityThreshold * 100).toFixed(0)}%`);
|
|
3367
|
+
const result = await collections["conversation-history"]["search"]({
|
|
3368
|
+
userPrompt,
|
|
3369
|
+
userId,
|
|
3370
|
+
threshold: similarityThreshold
|
|
3371
|
+
});
|
|
3372
|
+
if (!result) {
|
|
3373
|
+
logger.info("[ConversationSearch] No matching conversations found");
|
|
3374
|
+
return null;
|
|
3375
|
+
}
|
|
3376
|
+
if (!result.uiBlock) {
|
|
3377
|
+
logger.error("[ConversationSearch] No UI block in conversation search result");
|
|
3378
|
+
return null;
|
|
3379
|
+
}
|
|
3380
|
+
const similarity = result.similarity || 0;
|
|
3381
|
+
logger.info(`[ConversationSearch] Best match similarity: ${(similarity * 100).toFixed(2)}%`);
|
|
3382
|
+
if (similarity < similarityThreshold) {
|
|
3383
|
+
logger.info(
|
|
3384
|
+
`[ConversationSearch] Best match has similarity ${(similarity * 100).toFixed(2)}% but below threshold ${(similarityThreshold * 100).toFixed(2)}%`
|
|
3385
|
+
);
|
|
3386
|
+
return null;
|
|
3387
|
+
}
|
|
3388
|
+
logger.info(
|
|
3389
|
+
`[ConversationSearch] Found matching conversation with similarity ${(similarity * 100).toFixed(2)}%`
|
|
3390
|
+
);
|
|
3391
|
+
logger.debug(`[ConversationSearch] Matched prompt: "${result.metadata?.userPrompt?.substring(0, 50)}..."`);
|
|
3392
|
+
return result;
|
|
3393
|
+
} catch (error) {
|
|
3394
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3395
|
+
logger.warn(`[ConversationSearch] Error searching conversations: ${errorMsg}`);
|
|
3396
|
+
return null;
|
|
3397
|
+
}
|
|
3398
|
+
};
|
|
3399
|
+
var ConversationSearch = {
|
|
3400
|
+
searchConversations
|
|
3401
|
+
};
|
|
3402
|
+
var conversation_search_default = ConversationSearch;
|
|
3403
|
+
|
|
3208
3404
|
// src/userResponse/base-llm.ts
|
|
3209
3405
|
var BaseLLM = class {
|
|
3210
3406
|
constructor(config) {
|
|
@@ -3218,531 +3414,6 @@ var BaseLLM = class {
|
|
|
3218
3414
|
getApiKey(apiKey) {
|
|
3219
3415
|
return apiKey || this.apiKey || this.getDefaultApiKey();
|
|
3220
3416
|
}
|
|
3221
|
-
/**
|
|
3222
|
-
* Classify user question to determine the type and required visualizations
|
|
3223
|
-
*/
|
|
3224
|
-
async classifyUserQuestion(userPrompt, apiKey, logCollector, conversationHistory) {
|
|
3225
|
-
try {
|
|
3226
|
-
const prompts = await promptLoader.loadPrompts("classify", {
|
|
3227
|
-
USER_PROMPT: userPrompt,
|
|
3228
|
-
CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
|
|
3229
|
-
});
|
|
3230
|
-
const result = await LLM.stream(
|
|
3231
|
-
{
|
|
3232
|
-
sys: prompts.system,
|
|
3233
|
-
user: prompts.user
|
|
3234
|
-
},
|
|
3235
|
-
{
|
|
3236
|
-
model: this.model,
|
|
3237
|
-
maxTokens: 800,
|
|
3238
|
-
temperature: 0.2,
|
|
3239
|
-
apiKey: this.getApiKey(apiKey)
|
|
3240
|
-
},
|
|
3241
|
-
true
|
|
3242
|
-
// Parse as JSON
|
|
3243
|
-
);
|
|
3244
|
-
logCollector?.logExplanation(
|
|
3245
|
-
"User question classified",
|
|
3246
|
-
result.reasoning || "No reasoning provided",
|
|
3247
|
-
{
|
|
3248
|
-
questionType: result.questionType || "general",
|
|
3249
|
-
visualizations: result.visualizations || [],
|
|
3250
|
-
needsMultipleComponents: result.needsMultipleComponents || false
|
|
3251
|
-
}
|
|
3252
|
-
);
|
|
3253
|
-
return {
|
|
3254
|
-
questionType: result.questionType || "general",
|
|
3255
|
-
visualizations: result.visualizations || [],
|
|
3256
|
-
reasoning: result.reasoning || "No reasoning provided",
|
|
3257
|
-
needsMultipleComponents: result.needsMultipleComponents || false
|
|
3258
|
-
};
|
|
3259
|
-
} catch (error) {
|
|
3260
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3261
|
-
logger.error(`[${this.getProviderName()}] Error classifying user question: ${errorMsg}`);
|
|
3262
|
-
logger.debug(`[${this.getProviderName()}] Classification error details:`, error);
|
|
3263
|
-
throw error;
|
|
3264
|
-
}
|
|
3265
|
-
}
|
|
3266
|
-
/**
|
|
3267
|
-
* Enhanced function that validates and modifies the entire props object based on user request
|
|
3268
|
-
* This includes query, title, description, and config properties
|
|
3269
|
-
*/
|
|
3270
|
-
async validateAndModifyProps(userPrompt, originalProps, componentName, componentType, componentDescription, apiKey, logCollector, conversationHistory) {
|
|
3271
|
-
const schemaDoc = schema.generateSchemaDocumentation();
|
|
3272
|
-
try {
|
|
3273
|
-
const prompts = await promptLoader.loadPrompts("modify-props", {
|
|
3274
|
-
COMPONENT_NAME: componentName,
|
|
3275
|
-
COMPONENT_TYPE: componentType,
|
|
3276
|
-
COMPONENT_DESCRIPTION: componentDescription || "No description",
|
|
3277
|
-
SCHEMA_DOC: schemaDoc || "No schema available",
|
|
3278
|
-
DEFAULT_LIMIT: this.defaultLimit,
|
|
3279
|
-
USER_PROMPT: userPrompt,
|
|
3280
|
-
CURRENT_PROPS: JSON.stringify(originalProps, null, 2),
|
|
3281
|
-
CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
|
|
3282
|
-
});
|
|
3283
|
-
logger.debug("props-modification: System prompt\n", prompts.system.substring(0, 100), "\n\n\n", "User prompt:", prompts.user.substring(0, 50));
|
|
3284
|
-
const result = await LLM.stream(
|
|
3285
|
-
{
|
|
3286
|
-
sys: prompts.system,
|
|
3287
|
-
user: prompts.user
|
|
3288
|
-
},
|
|
3289
|
-
{
|
|
3290
|
-
model: this.model,
|
|
3291
|
-
maxTokens: 2500,
|
|
3292
|
-
temperature: 0.2,
|
|
3293
|
-
apiKey: this.getApiKey(apiKey)
|
|
3294
|
-
},
|
|
3295
|
-
true
|
|
3296
|
-
// Parse as JSON
|
|
3297
|
-
);
|
|
3298
|
-
const props = result.props || originalProps;
|
|
3299
|
-
if (props && props.query) {
|
|
3300
|
-
props.query = fixScalarSubqueries(props.query);
|
|
3301
|
-
props.query = ensureQueryLimit(props.query, this.defaultLimit);
|
|
3302
|
-
}
|
|
3303
|
-
if (props && props.query) {
|
|
3304
|
-
logCollector?.logQuery(
|
|
3305
|
-
"Props query modified",
|
|
3306
|
-
props.query,
|
|
3307
|
-
{
|
|
3308
|
-
modifications: result.modifications || [],
|
|
3309
|
-
reasoning: result.reasoning || "No modifications needed"
|
|
3310
|
-
}
|
|
3311
|
-
);
|
|
3312
|
-
}
|
|
3313
|
-
if (result.reasoning) {
|
|
3314
|
-
logCollector?.logExplanation(
|
|
3315
|
-
"Props modification explanation",
|
|
3316
|
-
result.reasoning,
|
|
3317
|
-
{ modifications: result.modifications || [] }
|
|
3318
|
-
);
|
|
3319
|
-
}
|
|
3320
|
-
return {
|
|
3321
|
-
props,
|
|
3322
|
-
isModified: result.isModified || false,
|
|
3323
|
-
reasoning: result.reasoning || "No modifications needed",
|
|
3324
|
-
modifications: result.modifications || []
|
|
3325
|
-
};
|
|
3326
|
-
} catch (error) {
|
|
3327
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3328
|
-
logger.error(`[${this.getProviderName()}] Error validating/modifying props: ${errorMsg}`);
|
|
3329
|
-
logger.debug(`[${this.getProviderName()}] Props validation error details:`, error);
|
|
3330
|
-
throw error;
|
|
3331
|
-
}
|
|
3332
|
-
}
|
|
3333
|
-
/**
|
|
3334
|
-
* Match and select a component from available components filtered by type
|
|
3335
|
-
* This picks the best matching component based on user prompt and modifies its props
|
|
3336
|
-
*/
|
|
3337
|
-
async generateAnalyticalComponent(userPrompt, components, preferredVisualizationType, apiKey, logCollector, conversationHistory) {
|
|
3338
|
-
try {
|
|
3339
|
-
const filteredComponents = preferredVisualizationType ? components.filter((c) => c.type === preferredVisualizationType) : components;
|
|
3340
|
-
if (filteredComponents.length === 0) {
|
|
3341
|
-
logCollector?.warn(
|
|
3342
|
-
`No components found of type ${preferredVisualizationType}`,
|
|
3343
|
-
"explanation",
|
|
3344
|
-
{ reason: "No matching components available for this visualization type" }
|
|
3345
|
-
);
|
|
3346
|
-
return {
|
|
3347
|
-
component: null,
|
|
3348
|
-
reasoning: `No components available of type ${preferredVisualizationType}`,
|
|
3349
|
-
isGenerated: false
|
|
3350
|
-
};
|
|
3351
|
-
}
|
|
3352
|
-
const componentsText = filteredComponents.map((comp, idx) => {
|
|
3353
|
-
const keywords = comp.keywords ? comp.keywords.join(", ") : "";
|
|
3354
|
-
const category = comp.category || "general";
|
|
3355
|
-
const propsPreview = comp.props ? JSON.stringify(comp.props, null, 2) : "No props";
|
|
3356
|
-
return `${idx + 1}. ID: ${comp.id}
|
|
3357
|
-
Name: ${comp.name}
|
|
3358
|
-
Type: ${comp.type}
|
|
3359
|
-
Category: ${category}
|
|
3360
|
-
Description: ${comp.description || "No description"}
|
|
3361
|
-
Keywords: ${keywords}
|
|
3362
|
-
Props Preview: ${propsPreview}`;
|
|
3363
|
-
}).join("\n\n");
|
|
3364
|
-
const visualizationConstraint = preferredVisualizationType ? `
|
|
3365
|
-
**IMPORTANT: Components are filtered to type ${preferredVisualizationType}. Select the best match.**
|
|
3366
|
-
` : "";
|
|
3367
|
-
const prompts = await promptLoader.loadPrompts("single-component", {
|
|
3368
|
-
COMPONENT_TYPE: preferredVisualizationType || "any",
|
|
3369
|
-
COMPONENTS_LIST: componentsText,
|
|
3370
|
-
VISUALIZATION_CONSTRAINT: visualizationConstraint,
|
|
3371
|
-
USER_PROMPT: userPrompt,
|
|
3372
|
-
CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
|
|
3373
|
-
});
|
|
3374
|
-
logger.debug("single-component: System prompt\n", prompts.system.substring(0, 100), "\n\n\n", "User prompt:", prompts.user.substring(0, 50));
|
|
3375
|
-
const result = await LLM.stream(
|
|
3376
|
-
{
|
|
3377
|
-
sys: prompts.system,
|
|
3378
|
-
user: prompts.user
|
|
3379
|
-
},
|
|
3380
|
-
{
|
|
3381
|
-
model: this.model,
|
|
3382
|
-
maxTokens: 2e3,
|
|
3383
|
-
temperature: 0.2,
|
|
3384
|
-
apiKey: this.getApiKey(apiKey)
|
|
3385
|
-
},
|
|
3386
|
-
true
|
|
3387
|
-
// Parse as JSON
|
|
3388
|
-
);
|
|
3389
|
-
if (!result.canGenerate || result.confidence < 50) {
|
|
3390
|
-
logCollector?.warn(
|
|
3391
|
-
"Cannot match component",
|
|
3392
|
-
"explanation",
|
|
3393
|
-
{ reason: result.reasoning || "Unable to find matching component for this question" }
|
|
3394
|
-
);
|
|
3395
|
-
return {
|
|
3396
|
-
component: null,
|
|
3397
|
-
reasoning: result.reasoning || "Unable to find matching component for this question",
|
|
3398
|
-
isGenerated: false
|
|
3399
|
-
};
|
|
3400
|
-
}
|
|
3401
|
-
const componentIndex = result.componentIndex;
|
|
3402
|
-
const componentId = result.componentId;
|
|
3403
|
-
let matchedComponent = null;
|
|
3404
|
-
if (componentId) {
|
|
3405
|
-
matchedComponent = filteredComponents.find((c) => c.id === componentId);
|
|
3406
|
-
}
|
|
3407
|
-
if (!matchedComponent && componentIndex) {
|
|
3408
|
-
matchedComponent = filteredComponents[componentIndex - 1];
|
|
3409
|
-
}
|
|
3410
|
-
if (!matchedComponent) {
|
|
3411
|
-
logCollector?.warn("Component not found in filtered list");
|
|
3412
|
-
return {
|
|
3413
|
-
component: null,
|
|
3414
|
-
reasoning: "Component not found in filtered list",
|
|
3415
|
-
isGenerated: false
|
|
3416
|
-
};
|
|
3417
|
-
}
|
|
3418
|
-
logCollector?.info(`Matched component: ${matchedComponent.name} (confidence: ${result.confidence}%)`);
|
|
3419
|
-
const propsValidation = await this.validateAndModifyProps(
|
|
3420
|
-
userPrompt,
|
|
3421
|
-
matchedComponent.props,
|
|
3422
|
-
matchedComponent.name,
|
|
3423
|
-
matchedComponent.type,
|
|
3424
|
-
matchedComponent.description,
|
|
3425
|
-
apiKey,
|
|
3426
|
-
logCollector,
|
|
3427
|
-
conversationHistory
|
|
3428
|
-
);
|
|
3429
|
-
const modifiedComponent = {
|
|
3430
|
-
...matchedComponent,
|
|
3431
|
-
props: propsValidation.props
|
|
3432
|
-
};
|
|
3433
|
-
logCollector?.logExplanation(
|
|
3434
|
-
"Analytical component selected and modified",
|
|
3435
|
-
result.reasoning || "Selected component based on analytical question",
|
|
3436
|
-
{
|
|
3437
|
-
componentName: matchedComponent.name,
|
|
3438
|
-
componentType: matchedComponent.type,
|
|
3439
|
-
confidence: result.confidence,
|
|
3440
|
-
propsModified: propsValidation.isModified
|
|
3441
|
-
}
|
|
3442
|
-
);
|
|
3443
|
-
return {
|
|
3444
|
-
component: modifiedComponent,
|
|
3445
|
-
reasoning: result.reasoning || "Selected and modified component based on analytical question",
|
|
3446
|
-
isGenerated: true
|
|
3447
|
-
};
|
|
3448
|
-
} catch (error) {
|
|
3449
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3450
|
-
logger.error(`[${this.getProviderName()}] Error generating analytical component: ${errorMsg}`);
|
|
3451
|
-
logger.debug(`[${this.getProviderName()}] Analytical component generation error details:`, error);
|
|
3452
|
-
throw error;
|
|
3453
|
-
}
|
|
3454
|
-
}
|
|
3455
|
-
/**
|
|
3456
|
-
* Generate container metadata (title and description) for multi-component dashboard
|
|
3457
|
-
*/
|
|
3458
|
-
async generateContainerMetadata(userPrompt, visualizationTypes, apiKey, logCollector, conversationHistory) {
|
|
3459
|
-
try {
|
|
3460
|
-
const prompts = await promptLoader.loadPrompts("container-metadata", {
|
|
3461
|
-
USER_PROMPT: userPrompt,
|
|
3462
|
-
VISUALIZATION_TYPES: visualizationTypes.join(", "),
|
|
3463
|
-
CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
|
|
3464
|
-
});
|
|
3465
|
-
const result = await LLM.stream(
|
|
3466
|
-
{
|
|
3467
|
-
sys: prompts.system,
|
|
3468
|
-
user: prompts.user
|
|
3469
|
-
},
|
|
3470
|
-
{
|
|
3471
|
-
model: this.model,
|
|
3472
|
-
maxTokens: 500,
|
|
3473
|
-
temperature: 0.3,
|
|
3474
|
-
apiKey: this.getApiKey(apiKey)
|
|
3475
|
-
},
|
|
3476
|
-
true
|
|
3477
|
-
// Parse as JSON
|
|
3478
|
-
);
|
|
3479
|
-
logCollector?.logExplanation(
|
|
3480
|
-
"Container metadata generated",
|
|
3481
|
-
`Generated title and description for multi-component dashboard`,
|
|
3482
|
-
{
|
|
3483
|
-
title: result.title,
|
|
3484
|
-
description: result.description,
|
|
3485
|
-
visualizationTypes
|
|
3486
|
-
}
|
|
3487
|
-
);
|
|
3488
|
-
return {
|
|
3489
|
-
title: result.title || `${userPrompt} - Dashboard`,
|
|
3490
|
-
description: result.description || `Multi-component dashboard showing ${visualizationTypes.join(", ")}`
|
|
3491
|
-
};
|
|
3492
|
-
} catch (error) {
|
|
3493
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3494
|
-
logger.error(`[${this.getProviderName()}] Error generating container metadata: ${errorMsg}`);
|
|
3495
|
-
logger.debug(`[${this.getProviderName()}] Container metadata error details:`, error);
|
|
3496
|
-
return {
|
|
3497
|
-
title: `${userPrompt} - Dashboard`,
|
|
3498
|
-
description: `Multi-component dashboard showing ${visualizationTypes.join(", ")}`
|
|
3499
|
-
};
|
|
3500
|
-
}
|
|
3501
|
-
}
|
|
3502
|
-
/**
|
|
3503
|
-
* Match component from a list with enhanced props modification
|
|
3504
|
-
*/
|
|
3505
|
-
async matchComponent(userPrompt, components, apiKey, logCollector, conversationHistory) {
|
|
3506
|
-
try {
|
|
3507
|
-
const componentsText = components.map((comp, idx) => {
|
|
3508
|
-
const keywords = comp.keywords ? comp.keywords.join(", ") : "";
|
|
3509
|
-
const category = comp.category || "general";
|
|
3510
|
-
return `${idx + 1}. ID: ${comp.id}
|
|
3511
|
-
Name: ${comp.name}
|
|
3512
|
-
Type: ${comp.type}
|
|
3513
|
-
Category: ${category}
|
|
3514
|
-
Description: ${comp.description || "No description"}
|
|
3515
|
-
Keywords: ${keywords}`;
|
|
3516
|
-
}).join("\n\n");
|
|
3517
|
-
const prompts = await promptLoader.loadPrompts("match-component", {
|
|
3518
|
-
COMPONENTS_TEXT: componentsText,
|
|
3519
|
-
USER_PROMPT: userPrompt,
|
|
3520
|
-
CONVERSATION_HISTORY: conversationHistory || "No previous conversation"
|
|
3521
|
-
});
|
|
3522
|
-
const result = await LLM.stream(
|
|
3523
|
-
{
|
|
3524
|
-
sys: prompts.system,
|
|
3525
|
-
user: prompts.user
|
|
3526
|
-
},
|
|
3527
|
-
{
|
|
3528
|
-
model: this.model,
|
|
3529
|
-
maxTokens: 800,
|
|
3530
|
-
temperature: 0.2,
|
|
3531
|
-
apiKey: this.getApiKey(apiKey)
|
|
3532
|
-
},
|
|
3533
|
-
true
|
|
3534
|
-
// Parse as JSON
|
|
3535
|
-
);
|
|
3536
|
-
const componentIndex = result.componentIndex;
|
|
3537
|
-
const componentId = result.componentId;
|
|
3538
|
-
const confidence = result.confidence || 0;
|
|
3539
|
-
let component = null;
|
|
3540
|
-
if (componentId) {
|
|
3541
|
-
component = components.find((c) => c.id === componentId);
|
|
3542
|
-
}
|
|
3543
|
-
if (!component && componentIndex) {
|
|
3544
|
-
component = components[componentIndex - 1];
|
|
3545
|
-
}
|
|
3546
|
-
const matchedMsg = `${this.getProviderName()} matched component: ${component?.name || "None"}`;
|
|
3547
|
-
logger.info(`[${this.getProviderName()}] \u2713 ${matchedMsg}`);
|
|
3548
|
-
logCollector?.info(matchedMsg);
|
|
3549
|
-
if (result.alternativeMatches && result.alternativeMatches.length > 0) {
|
|
3550
|
-
logger.debug(`[${this.getProviderName()}] Alternative matches found: ${result.alternativeMatches.length}`);
|
|
3551
|
-
const altMatches = result.alternativeMatches.map(
|
|
3552
|
-
(alt) => `${components[alt.index - 1]?.name} (${alt.score}%): ${alt.reason}`
|
|
3553
|
-
).join(" | ");
|
|
3554
|
-
logCollector?.info(`Alternative matches: ${altMatches}`);
|
|
3555
|
-
result.alternativeMatches.forEach((alt) => {
|
|
3556
|
-
logger.debug(`[${this.getProviderName()}] - ${components[alt.index - 1]?.name} (${alt.score}%): ${alt.reason}`);
|
|
3557
|
-
});
|
|
3558
|
-
}
|
|
3559
|
-
if (!component) {
|
|
3560
|
-
const noMatchMsg = `No matching component found (confidence: ${confidence}%)`;
|
|
3561
|
-
logger.warn(`[${this.getProviderName()}] \u2717 ${noMatchMsg}`);
|
|
3562
|
-
logCollector?.warn(noMatchMsg);
|
|
3563
|
-
const genMsg = "Attempting to match component from analytical question...";
|
|
3564
|
-
logger.info(`[${this.getProviderName()}] \u2713 ${genMsg}`);
|
|
3565
|
-
logCollector?.info(genMsg);
|
|
3566
|
-
const generatedResult = await this.generateAnalyticalComponent(userPrompt, components, void 0, apiKey, logCollector, conversationHistory);
|
|
3567
|
-
if (generatedResult.component) {
|
|
3568
|
-
const genSuccessMsg = `Successfully matched component: ${generatedResult.component.name}`;
|
|
3569
|
-
logCollector?.info(genSuccessMsg);
|
|
3570
|
-
return {
|
|
3571
|
-
component: generatedResult.component,
|
|
3572
|
-
reasoning: generatedResult.reasoning,
|
|
3573
|
-
method: `${this.getProviderName()}-generated`,
|
|
3574
|
-
confidence: 100,
|
|
3575
|
-
// Generated components are considered 100% match to the question
|
|
3576
|
-
propsModified: false,
|
|
3577
|
-
queryModified: false
|
|
3578
|
-
};
|
|
3579
|
-
}
|
|
3580
|
-
logCollector?.error("Failed to match component");
|
|
3581
|
-
return {
|
|
3582
|
-
component: null,
|
|
3583
|
-
reasoning: result.reasoning || "No matching component found and unable to match component",
|
|
3584
|
-
method: `${this.getProviderName()}-llm`,
|
|
3585
|
-
confidence
|
|
3586
|
-
};
|
|
3587
|
-
}
|
|
3588
|
-
let propsModified = false;
|
|
3589
|
-
let propsModifications = [];
|
|
3590
|
-
let queryModified = false;
|
|
3591
|
-
let queryReasoning = "";
|
|
3592
|
-
if (component && component.props) {
|
|
3593
|
-
const propsValidation = await this.validateAndModifyProps(
|
|
3594
|
-
userPrompt,
|
|
3595
|
-
component.props,
|
|
3596
|
-
component.name,
|
|
3597
|
-
component.type,
|
|
3598
|
-
component.description,
|
|
3599
|
-
apiKey,
|
|
3600
|
-
logCollector,
|
|
3601
|
-
conversationHistory
|
|
3602
|
-
);
|
|
3603
|
-
const originalQuery = component.props.query;
|
|
3604
|
-
const modifiedQuery = propsValidation.props.query;
|
|
3605
|
-
component = {
|
|
3606
|
-
...component,
|
|
3607
|
-
props: propsValidation.props
|
|
3608
|
-
};
|
|
3609
|
-
propsModified = propsValidation.isModified;
|
|
3610
|
-
propsModifications = propsValidation.modifications;
|
|
3611
|
-
queryModified = originalQuery !== modifiedQuery;
|
|
3612
|
-
queryReasoning = propsValidation.reasoning;
|
|
3613
|
-
}
|
|
3614
|
-
return {
|
|
3615
|
-
component,
|
|
3616
|
-
reasoning: result.reasoning || "No reasoning provided",
|
|
3617
|
-
queryModified,
|
|
3618
|
-
queryReasoning,
|
|
3619
|
-
propsModified,
|
|
3620
|
-
propsModifications,
|
|
3621
|
-
method: `${this.getProviderName()}-llm`,
|
|
3622
|
-
confidence
|
|
3623
|
-
};
|
|
3624
|
-
} catch (error) {
|
|
3625
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3626
|
-
logger.error(`[${this.getProviderName()}] Error matching component: ${errorMsg}`);
|
|
3627
|
-
logger.debug(`[${this.getProviderName()}] Component matching error details:`, error);
|
|
3628
|
-
logCollector?.error(`Error matching component: ${errorMsg}`);
|
|
3629
|
-
throw error;
|
|
3630
|
-
}
|
|
3631
|
-
}
|
|
3632
|
-
/**
|
|
3633
|
-
* Match multiple components for analytical questions by visualization types
|
|
3634
|
-
* This is used when the user needs multiple visualizations
|
|
3635
|
-
*/
|
|
3636
|
-
async generateMultipleAnalyticalComponents(userPrompt, availableComponents, visualizationTypes, apiKey, logCollector, conversationHistory) {
|
|
3637
|
-
try {
|
|
3638
|
-
console.log("\u2713 Matching multiple components:", visualizationTypes);
|
|
3639
|
-
const components = [];
|
|
3640
|
-
for (const vizType of visualizationTypes) {
|
|
3641
|
-
const result = await this.generateAnalyticalComponent(userPrompt, availableComponents, vizType, apiKey, logCollector, conversationHistory);
|
|
3642
|
-
if (result.component) {
|
|
3643
|
-
components.push(result.component);
|
|
3644
|
-
}
|
|
3645
|
-
}
|
|
3646
|
-
if (components.length === 0) {
|
|
3647
|
-
return {
|
|
3648
|
-
components: [],
|
|
3649
|
-
reasoning: "Failed to match any components",
|
|
3650
|
-
isGenerated: false
|
|
3651
|
-
};
|
|
3652
|
-
}
|
|
3653
|
-
return {
|
|
3654
|
-
components,
|
|
3655
|
-
reasoning: `Matched ${components.length} components: ${visualizationTypes.join(", ")}`,
|
|
3656
|
-
isGenerated: true
|
|
3657
|
-
};
|
|
3658
|
-
} catch (error) {
|
|
3659
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3660
|
-
logger.error(`[${this.getProviderName()}] Error matching multiple analytical components: ${errorMsg}`);
|
|
3661
|
-
logger.debug(`[${this.getProviderName()}] Multiple components matching error details:`, error);
|
|
3662
|
-
return {
|
|
3663
|
-
components: [],
|
|
3664
|
-
reasoning: "Error occurred while matching components",
|
|
3665
|
-
isGenerated: false
|
|
3666
|
-
};
|
|
3667
|
-
}
|
|
3668
|
-
}
|
|
3669
|
-
/**
|
|
3670
|
-
* Match multiple components and wrap them in a container
|
|
3671
|
-
*/
|
|
3672
|
-
async generateMultiComponentResponse(userPrompt, availableComponents, visualizationTypes, apiKey, logCollector, conversationHistory) {
|
|
3673
|
-
try {
|
|
3674
|
-
const matchResult = await this.generateMultipleAnalyticalComponents(
|
|
3675
|
-
userPrompt,
|
|
3676
|
-
availableComponents,
|
|
3677
|
-
visualizationTypes,
|
|
3678
|
-
apiKey,
|
|
3679
|
-
logCollector,
|
|
3680
|
-
conversationHistory
|
|
3681
|
-
);
|
|
3682
|
-
if (!matchResult.isGenerated || matchResult.components.length === 0) {
|
|
3683
|
-
return {
|
|
3684
|
-
containerComponent: null,
|
|
3685
|
-
reasoning: matchResult.reasoning || "Unable to match multi-component dashboard",
|
|
3686
|
-
isGenerated: false
|
|
3687
|
-
};
|
|
3688
|
-
}
|
|
3689
|
-
const generatedComponents = matchResult.components;
|
|
3690
|
-
generatedComponents.forEach((component, index) => {
|
|
3691
|
-
if (component.props.query) {
|
|
3692
|
-
logCollector?.logQuery(
|
|
3693
|
-
`Multi-component query generated (${index + 1}/${generatedComponents.length})`,
|
|
3694
|
-
component.props.query,
|
|
3695
|
-
{
|
|
3696
|
-
componentType: component.type,
|
|
3697
|
-
title: component.props.title,
|
|
3698
|
-
position: index + 1,
|
|
3699
|
-
totalComponents: generatedComponents.length
|
|
3700
|
-
}
|
|
3701
|
-
);
|
|
3702
|
-
}
|
|
3703
|
-
});
|
|
3704
|
-
const containerTitle = `${userPrompt} - Dashboard`;
|
|
3705
|
-
const containerDescription = `Multi-component dashboard showing ${visualizationTypes.join(", ")}`;
|
|
3706
|
-
logCollector?.logExplanation(
|
|
3707
|
-
"Multi-component dashboard matched",
|
|
3708
|
-
matchResult.reasoning || `Matched ${generatedComponents.length} components for comprehensive analysis`,
|
|
3709
|
-
{
|
|
3710
|
-
totalComponents: generatedComponents.length,
|
|
3711
|
-
componentTypes: generatedComponents.map((c) => c.type),
|
|
3712
|
-
componentNames: generatedComponents.map((c) => c.name),
|
|
3713
|
-
containerTitle,
|
|
3714
|
-
containerDescription
|
|
3715
|
-
}
|
|
3716
|
-
);
|
|
3717
|
-
const containerComponent = {
|
|
3718
|
-
id: `multi_container_${Date.now()}`,
|
|
3719
|
-
name: "MultiComponentContainer",
|
|
3720
|
-
type: "Container",
|
|
3721
|
-
description: containerDescription,
|
|
3722
|
-
category: "dynamic",
|
|
3723
|
-
keywords: ["multi", "container", "dashboard"],
|
|
3724
|
-
props: {
|
|
3725
|
-
config: {
|
|
3726
|
-
components: generatedComponents,
|
|
3727
|
-
layout: "grid",
|
|
3728
|
-
spacing: 24,
|
|
3729
|
-
title: containerTitle,
|
|
3730
|
-
description: containerDescription
|
|
3731
|
-
}
|
|
3732
|
-
}
|
|
3733
|
-
};
|
|
3734
|
-
return {
|
|
3735
|
-
containerComponent,
|
|
3736
|
-
reasoning: matchResult.reasoning || `Matched multi-component dashboard with ${generatedComponents.length} components`,
|
|
3737
|
-
isGenerated: true
|
|
3738
|
-
};
|
|
3739
|
-
} catch (error) {
|
|
3740
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3741
|
-
logger.error(`[${this.getProviderName()}] Error generating multi-component response: ${errorMsg}`);
|
|
3742
|
-
logger.debug(`[${this.getProviderName()}] Multi-component response error details:`, error);
|
|
3743
|
-
throw error;
|
|
3744
|
-
}
|
|
3745
|
-
}
|
|
3746
3417
|
/**
|
|
3747
3418
|
* Match components from text response suggestions and generate follow-up questions
|
|
3748
3419
|
* Takes a text response with component suggestions (c1:type format) and matches with available components
|
|
@@ -3968,148 +3639,136 @@ var BaseLLM = class {
|
|
|
3968
3639
|
}
|
|
3969
3640
|
}
|
|
3970
3641
|
/**
|
|
3971
|
-
*
|
|
3972
|
-
*
|
|
3973
|
-
* @param userPrompt - The user's question/request
|
|
3974
|
-
* @param availableTools - Array of available external tools
|
|
3975
|
-
* @param apiKey - Optional API key for LLM
|
|
3976
|
-
* @param logCollector - Optional log collector
|
|
3977
|
-
* @returns Object containing tool execution results and summary
|
|
3642
|
+
* Classify user question into category and detect external tools needed
|
|
3643
|
+
* Determines if question is for data analysis, requires external tools, or needs text response
|
|
3978
3644
|
*/
|
|
3979
|
-
async
|
|
3980
|
-
const MAX_TOOL_ATTEMPTS = 3;
|
|
3981
|
-
const toolResults = [];
|
|
3645
|
+
async classifyQuestionCategory(userPrompt, apiKey, logCollector, conversationHistory, externalTools) {
|
|
3982
3646
|
try {
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
required.push(key);
|
|
3994
|
-
});
|
|
3995
|
-
return {
|
|
3996
|
-
name: tool.id,
|
|
3997
|
-
description: tool.description,
|
|
3998
|
-
input_schema: {
|
|
3999
|
-
type: "object",
|
|
4000
|
-
properties,
|
|
4001
|
-
required
|
|
4002
|
-
}
|
|
4003
|
-
};
|
|
3647
|
+
const availableToolsDoc = externalTools && externalTools.length > 0 ? externalTools.map((tool) => {
|
|
3648
|
+
const paramsStr = Object.entries(tool.params || {}).map(([key, type]) => `${key}: ${type}`).join(", ");
|
|
3649
|
+
return `- **${tool.name}** (id: ${tool.id})
|
|
3650
|
+
Description: ${tool.description}
|
|
3651
|
+
Parameters: ${paramsStr}`;
|
|
3652
|
+
}).join("\n\n") : "No external tools available";
|
|
3653
|
+
const prompts = await promptLoader.loadPrompts("category-classification", {
|
|
3654
|
+
USER_PROMPT: userPrompt,
|
|
3655
|
+
CONVERSATION_HISTORY: conversationHistory || "No previous conversation",
|
|
3656
|
+
AVAILABLE_TOOLS: availableToolsDoc
|
|
4004
3657
|
});
|
|
4005
|
-
const
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
});
|
|
4028
|
-
throw new Error(errorMsg);
|
|
4029
|
-
}
|
|
4030
|
-
try {
|
|
4031
|
-
logger.debug(`[${this.getProviderName()}] Tool ${tool.name} parameters:`, toolInput);
|
|
4032
|
-
const result2 = await tool.fn(toolInput);
|
|
4033
|
-
logger.info(`[${this.getProviderName()}] Tool ${tool.name} executed successfully`);
|
|
4034
|
-
logCollector?.info(`\u2713 ${tool.name} completed successfully`);
|
|
4035
|
-
toolResults.push({
|
|
4036
|
-
toolName: tool.name,
|
|
4037
|
-
toolId: tool.id,
|
|
4038
|
-
result: result2
|
|
4039
|
-
});
|
|
4040
|
-
return JSON.stringify(result2, null, 2);
|
|
4041
|
-
} catch (error) {
|
|
4042
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4043
|
-
logger.error(`[${this.getProviderName()}] Tool ${tool.name} failed (attempt ${attempts}): ${errorMsg}`);
|
|
4044
|
-
logCollector?.error(`\u2717 ${tool.name} failed: ${errorMsg}`);
|
|
4045
|
-
if (attempts >= MAX_TOOL_ATTEMPTS) {
|
|
4046
|
-
toolResults.push({
|
|
4047
|
-
toolName: tool.name,
|
|
4048
|
-
toolId: tool.id,
|
|
4049
|
-
result: null,
|
|
4050
|
-
error: errorMsg
|
|
4051
|
-
});
|
|
4052
|
-
}
|
|
4053
|
-
throw new Error(`Tool execution failed: ${errorMsg}`);
|
|
3658
|
+
const result = await LLM.stream(
|
|
3659
|
+
{
|
|
3660
|
+
sys: prompts.system,
|
|
3661
|
+
user: prompts.user
|
|
3662
|
+
},
|
|
3663
|
+
{
|
|
3664
|
+
model: this.model,
|
|
3665
|
+
maxTokens: 1e3,
|
|
3666
|
+
temperature: 0.2,
|
|
3667
|
+
apiKey: this.getApiKey(apiKey)
|
|
3668
|
+
},
|
|
3669
|
+
true
|
|
3670
|
+
// Parse as JSON
|
|
3671
|
+
);
|
|
3672
|
+
logCollector?.logExplanation(
|
|
3673
|
+
"Question category classified",
|
|
3674
|
+
result.reasoning || "No reasoning provided",
|
|
3675
|
+
{
|
|
3676
|
+
category: result.category,
|
|
3677
|
+
externalTools: result.externalTools || [],
|
|
3678
|
+
dataAnalysisType: result.dataAnalysisType,
|
|
3679
|
+
confidence: result.confidence
|
|
4054
3680
|
}
|
|
3681
|
+
);
|
|
3682
|
+
return {
|
|
3683
|
+
category: result.category || "data_analysis",
|
|
3684
|
+
externalTools: result.externalTools || [],
|
|
3685
|
+
dataAnalysisType: result.dataAnalysisType,
|
|
3686
|
+
reasoning: result.reasoning || "No reasoning provided",
|
|
3687
|
+
confidence: result.confidence || 0
|
|
4055
3688
|
};
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
3689
|
+
} catch (error) {
|
|
3690
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3691
|
+
logger.error(`[${this.getProviderName()}] Error classifying question category: ${errorMsg}`);
|
|
3692
|
+
logger.debug(`[${this.getProviderName()}] Category classification error details:`, error);
|
|
3693
|
+
throw error;
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
/**
|
|
3697
|
+
* Adapt UI block parameters based on current user question
|
|
3698
|
+
* Takes a matched UI block from semantic search and modifies its props to answer the new question
|
|
3699
|
+
*/
|
|
3700
|
+
async adaptUIBlockParameters(currentUserPrompt, originalUserPrompt, matchedUIBlock, apiKey, logCollector) {
|
|
3701
|
+
try {
|
|
3702
|
+
if (!matchedUIBlock || !matchedUIBlock.generatedComponentMetadata) {
|
|
3703
|
+
return {
|
|
3704
|
+
success: false,
|
|
3705
|
+
explanation: "No component found in matched UI block"
|
|
3706
|
+
};
|
|
3707
|
+
}
|
|
3708
|
+
const component = matchedUIBlock.generatedComponentMetadata;
|
|
3709
|
+
const schemaDoc = schema.generateSchemaDocumentation();
|
|
3710
|
+
const prompts = await promptLoader.loadPrompts("adapt-ui-block-params", {
|
|
3711
|
+
ORIGINAL_USER_PROMPT: originalUserPrompt,
|
|
3712
|
+
CURRENT_USER_PROMPT: currentUserPrompt,
|
|
3713
|
+
MATCHED_UI_BLOCK_COMPONENT: JSON.stringify(component, null, 2),
|
|
3714
|
+
COMPONENT_PROPS: JSON.stringify(component.props, null, 2),
|
|
3715
|
+
SCHEMA_DOC: schemaDoc || "No schema available"
|
|
4066
3716
|
});
|
|
4067
|
-
|
|
4068
|
-
logCollector?.info("Analyzing request and executing external tools...");
|
|
4069
|
-
const result = await LLM.streamWithTools(
|
|
3717
|
+
const result = await LLM.stream(
|
|
4070
3718
|
{
|
|
4071
3719
|
sys: prompts.system,
|
|
4072
3720
|
user: prompts.user
|
|
4073
3721
|
},
|
|
4074
|
-
llmTools,
|
|
4075
|
-
toolHandler,
|
|
4076
3722
|
{
|
|
4077
3723
|
model: this.model,
|
|
4078
3724
|
maxTokens: 2e3,
|
|
4079
3725
|
temperature: 0.2,
|
|
4080
3726
|
apiKey: this.getApiKey(apiKey)
|
|
4081
3727
|
},
|
|
4082
|
-
|
|
4083
|
-
//
|
|
3728
|
+
true
|
|
3729
|
+
// Parse as JSON
|
|
4084
3730
|
);
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
3731
|
+
if (!result.success) {
|
|
3732
|
+
logger.info(
|
|
3733
|
+
`[${this.getProviderName()}] Could not adapt UI block: ${result.reason}`
|
|
3734
|
+
);
|
|
3735
|
+
logCollector?.warn(
|
|
3736
|
+
"Could not adapt matched UI block",
|
|
3737
|
+
"explanation",
|
|
3738
|
+
{ reason: result.reason }
|
|
3739
|
+
);
|
|
3740
|
+
return {
|
|
3741
|
+
success: false,
|
|
3742
|
+
explanation: result.explanation || "Adaptation not possible"
|
|
3743
|
+
};
|
|
4095
3744
|
}
|
|
4096
|
-
if (
|
|
4097
|
-
|
|
3745
|
+
if (result.adaptedComponent?.props?.query) {
|
|
3746
|
+
result.adaptedComponent.props.query = ensureQueryLimit(
|
|
3747
|
+
result.adaptedComponent.props.query,
|
|
3748
|
+
this.defaultLimit
|
|
3749
|
+
);
|
|
4098
3750
|
}
|
|
4099
|
-
|
|
3751
|
+
logCollector?.logExplanation(
|
|
3752
|
+
"UI block parameters adapted",
|
|
3753
|
+
result.explanation || "Parameters adapted successfully",
|
|
3754
|
+
{
|
|
3755
|
+
parametersChanged: result.parametersChanged || [],
|
|
3756
|
+
componentType: result.adaptedComponent?.type
|
|
3757
|
+
}
|
|
3758
|
+
);
|
|
4100
3759
|
return {
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
3760
|
+
success: true,
|
|
3761
|
+
adaptedComponent: result.adaptedComponent,
|
|
3762
|
+
parametersChanged: result.parametersChanged,
|
|
3763
|
+
explanation: result.explanation || "Parameters adapted successfully"
|
|
4104
3764
|
};
|
|
4105
3765
|
} catch (error) {
|
|
4106
3766
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4107
|
-
logger.error(`[${this.getProviderName()}] Error
|
|
4108
|
-
|
|
3767
|
+
logger.error(`[${this.getProviderName()}] Error adapting UI block parameters: ${errorMsg}`);
|
|
3768
|
+
logger.debug(`[${this.getProviderName()}] Adaptation error details:`, error);
|
|
4109
3769
|
return {
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
hasResults: false
|
|
3770
|
+
success: false,
|
|
3771
|
+
explanation: `Error adapting parameters: ${errorMsg}`
|
|
4113
3772
|
};
|
|
4114
3773
|
}
|
|
4115
3774
|
}
|
|
@@ -4128,32 +3787,24 @@ ${paramsText}`;
|
|
|
4128
3787
|
logger.debug(`[${this.getProviderName()}] Starting text response generation`);
|
|
4129
3788
|
logger.debug(`[${this.getProviderName()}] User prompt: "${userPrompt.substring(0, 50)}..."`);
|
|
4130
3789
|
try {
|
|
4131
|
-
let
|
|
3790
|
+
let availableToolsDoc = "No external tools are available for this request.";
|
|
4132
3791
|
if (externalTools && externalTools.length > 0) {
|
|
4133
|
-
logger.info(`[${this.getProviderName()}]
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
const toolResultsText = toolExecution.toolResults.map((tr) => {
|
|
4142
|
-
if (tr.error) {
|
|
4143
|
-
return `**${tr.toolName}** (Failed): ${tr.error}`;
|
|
3792
|
+
logger.info(`[${this.getProviderName()}] External tools available: ${externalTools.map((t) => t.name).join(", ")}`);
|
|
3793
|
+
availableToolsDoc = "\u26A0\uFE0F **EXECUTE THESE TOOLS IMMEDIATELY** \u26A0\uFE0F\n\nThe following external tools have been identified as necessary for this request. You MUST call them:\n\n" + externalTools.map((tool, idx) => {
|
|
3794
|
+
const paramsText = Object.entries(tool.params || {}).map(([key, value]) => {
|
|
3795
|
+
const valueType = typeof value;
|
|
3796
|
+
if (valueType === "string" && ["string", "number", "integer", "boolean", "array", "object"].includes(String(value).toLowerCase())) {
|
|
3797
|
+
return `- ${key}: ${value}`;
|
|
3798
|
+
} else {
|
|
3799
|
+
return `- ${key}: ${JSON.stringify(value)} (default value - use this)`;
|
|
4144
3800
|
}
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
${
|
|
4151
|
-
|
|
4152
|
-
${toolResultsText}`;
|
|
4153
|
-
logger.info(`[${this.getProviderName()}] External tools executed, results available`);
|
|
4154
|
-
} else {
|
|
4155
|
-
logger.info(`[${this.getProviderName()}] No external tools were needed`);
|
|
4156
|
-
}
|
|
3801
|
+
}).join("\n ");
|
|
3802
|
+
return `${idx + 1}. **${tool.name}** (ID: ${tool.id})
|
|
3803
|
+
Description: ${tool.description}
|
|
3804
|
+
**ACTION REQUIRED**: Call this tool with the parameters below
|
|
3805
|
+
Parameters:
|
|
3806
|
+
${paramsText}`;
|
|
3807
|
+
}).join("\n\n");
|
|
4157
3808
|
}
|
|
4158
3809
|
const schemaDoc = schema.generateSchemaDocumentation();
|
|
4159
3810
|
const knowledgeBaseContext = await knowledge_base_default.getKnowledgeBase({
|
|
@@ -4162,13 +3813,12 @@ ${toolResultsText}`;
|
|
|
4162
3813
|
topK: 1
|
|
4163
3814
|
});
|
|
4164
3815
|
logger.file("\n=============================\nknowledge base context:", knowledgeBaseContext);
|
|
4165
|
-
logger.file("\n=============================\nexternal tool context:", externalToolContext);
|
|
4166
3816
|
const prompts = await promptLoader.loadPrompts("text-response", {
|
|
4167
3817
|
USER_PROMPT: userPrompt,
|
|
4168
3818
|
CONVERSATION_HISTORY: conversationHistory || "No previous conversation",
|
|
4169
3819
|
SCHEMA_DOC: schemaDoc,
|
|
4170
3820
|
KNOWLEDGE_BASE_CONTEXT: knowledgeBaseContext || "No additional knowledge base context available.",
|
|
4171
|
-
|
|
3821
|
+
AVAILABLE_EXTERNAL_TOOLS: availableToolsDoc
|
|
4172
3822
|
});
|
|
4173
3823
|
logger.file("\n=============================\nsystem prompt:", prompts.system);
|
|
4174
3824
|
logger.file("\n=============================\nuser prompt:", prompts.user);
|
|
@@ -4190,11 +3840,88 @@ ${toolResultsText}`;
|
|
|
4190
3840
|
description: "Brief explanation of what this query does and why it answers the user's question."
|
|
4191
3841
|
}
|
|
4192
3842
|
},
|
|
4193
|
-
required: ["query"]
|
|
3843
|
+
required: ["query"],
|
|
3844
|
+
additionalProperties: false
|
|
4194
3845
|
}
|
|
4195
3846
|
}];
|
|
3847
|
+
if (externalTools && externalTools.length > 0) {
|
|
3848
|
+
externalTools.forEach((tool) => {
|
|
3849
|
+
logger.info(`[${this.getProviderName()}] Processing external tool:`, JSON.stringify(tool, null, 2));
|
|
3850
|
+
const properties = {};
|
|
3851
|
+
const required = [];
|
|
3852
|
+
Object.entries(tool.params || {}).forEach(([key, typeOrValue]) => {
|
|
3853
|
+
let schemaType;
|
|
3854
|
+
let hasDefaultValue = false;
|
|
3855
|
+
let defaultValue;
|
|
3856
|
+
const valueType = typeof typeOrValue;
|
|
3857
|
+
if (valueType === "number") {
|
|
3858
|
+
schemaType = Number.isInteger(typeOrValue) ? "integer" : "number";
|
|
3859
|
+
hasDefaultValue = true;
|
|
3860
|
+
defaultValue = typeOrValue;
|
|
3861
|
+
} else if (valueType === "boolean") {
|
|
3862
|
+
schemaType = "boolean";
|
|
3863
|
+
hasDefaultValue = true;
|
|
3864
|
+
defaultValue = typeOrValue;
|
|
3865
|
+
} else if (Array.isArray(typeOrValue)) {
|
|
3866
|
+
schemaType = "array";
|
|
3867
|
+
hasDefaultValue = true;
|
|
3868
|
+
defaultValue = typeOrValue;
|
|
3869
|
+
} else if (valueType === "object" && typeOrValue !== null) {
|
|
3870
|
+
schemaType = "object";
|
|
3871
|
+
hasDefaultValue = true;
|
|
3872
|
+
defaultValue = typeOrValue;
|
|
3873
|
+
} else {
|
|
3874
|
+
const typeStr = String(typeOrValue).toLowerCase().trim();
|
|
3875
|
+
if (typeStr === "string" || typeStr === "str") {
|
|
3876
|
+
schemaType = "string";
|
|
3877
|
+
} else if (typeStr === "number" || typeStr === "num" || typeStr === "float" || typeStr === "double") {
|
|
3878
|
+
schemaType = "number";
|
|
3879
|
+
} else if (typeStr === "integer" || typeStr === "int") {
|
|
3880
|
+
schemaType = "integer";
|
|
3881
|
+
} else if (typeStr === "boolean" || typeStr === "bool") {
|
|
3882
|
+
schemaType = "boolean";
|
|
3883
|
+
} else if (typeStr === "array" || typeStr === "list") {
|
|
3884
|
+
schemaType = "array";
|
|
3885
|
+
} else if (typeStr === "object" || typeStr === "dict") {
|
|
3886
|
+
schemaType = "object";
|
|
3887
|
+
} else {
|
|
3888
|
+
schemaType = "string";
|
|
3889
|
+
hasDefaultValue = true;
|
|
3890
|
+
defaultValue = typeOrValue;
|
|
3891
|
+
}
|
|
3892
|
+
}
|
|
3893
|
+
const propertySchema = {
|
|
3894
|
+
type: schemaType,
|
|
3895
|
+
description: `${key} parameter for ${tool.name}`
|
|
3896
|
+
};
|
|
3897
|
+
if (hasDefaultValue) {
|
|
3898
|
+
propertySchema.default = defaultValue;
|
|
3899
|
+
} else {
|
|
3900
|
+
required.push(key);
|
|
3901
|
+
}
|
|
3902
|
+
properties[key] = propertySchema;
|
|
3903
|
+
});
|
|
3904
|
+
const inputSchema = {
|
|
3905
|
+
type: "object",
|
|
3906
|
+
properties,
|
|
3907
|
+
additionalProperties: false
|
|
3908
|
+
};
|
|
3909
|
+
if (required.length > 0) {
|
|
3910
|
+
inputSchema.required = required;
|
|
3911
|
+
}
|
|
3912
|
+
tools.push({
|
|
3913
|
+
name: tool.id,
|
|
3914
|
+
description: tool.description,
|
|
3915
|
+
input_schema: inputSchema
|
|
3916
|
+
});
|
|
3917
|
+
});
|
|
3918
|
+
logger.info(`[${this.getProviderName()}] Added ${externalTools.length} external tools to tool calling capability`);
|
|
3919
|
+
logger.info(`[${this.getProviderName()}] Complete tools array:`, JSON.stringify(tools, null, 2));
|
|
3920
|
+
}
|
|
4196
3921
|
const queryAttempts = /* @__PURE__ */ new Map();
|
|
4197
3922
|
const MAX_QUERY_ATTEMPTS = 6;
|
|
3923
|
+
const toolAttempts = /* @__PURE__ */ new Map();
|
|
3924
|
+
const MAX_TOOL_ATTEMPTS = 3;
|
|
4198
3925
|
let maxAttemptsReached = false;
|
|
4199
3926
|
let fullStreamedText = "";
|
|
4200
3927
|
const wrappedStreamCallback = streamCallback ? (chunk) => {
|
|
@@ -4336,8 +4063,75 @@ ${errorMsg}
|
|
|
4336
4063
|
}
|
|
4337
4064
|
throw new Error(`Query execution failed: ${errorMsg}`);
|
|
4338
4065
|
}
|
|
4066
|
+
} else {
|
|
4067
|
+
const externalTool = externalTools?.find((t) => t.id === toolName);
|
|
4068
|
+
if (externalTool) {
|
|
4069
|
+
const attempts = (toolAttempts.get(toolName) || 0) + 1;
|
|
4070
|
+
toolAttempts.set(toolName, attempts);
|
|
4071
|
+
logger.info(`[${this.getProviderName()}] Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})`);
|
|
4072
|
+
logCollector?.info(`Executing external tool: ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...`);
|
|
4073
|
+
if (attempts > MAX_TOOL_ATTEMPTS) {
|
|
4074
|
+
const errorMsg = `Maximum attempts (${MAX_TOOL_ATTEMPTS}) reached for tool: ${externalTool.name}`;
|
|
4075
|
+
logger.error(`[${this.getProviderName()}] ${errorMsg}`);
|
|
4076
|
+
logCollector?.error(errorMsg);
|
|
4077
|
+
if (wrappedStreamCallback) {
|
|
4078
|
+
wrappedStreamCallback(`
|
|
4079
|
+
|
|
4080
|
+
\u274C ${errorMsg}
|
|
4081
|
+
|
|
4082
|
+
Please try rephrasing your request or contact support.
|
|
4083
|
+
|
|
4084
|
+
`);
|
|
4085
|
+
}
|
|
4086
|
+
throw new Error(errorMsg);
|
|
4087
|
+
}
|
|
4088
|
+
try {
|
|
4089
|
+
if (wrappedStreamCallback) {
|
|
4090
|
+
if (attempts === 1) {
|
|
4091
|
+
wrappedStreamCallback(`
|
|
4092
|
+
|
|
4093
|
+
\u{1F517} **Executing ${externalTool.name}...**
|
|
4094
|
+
|
|
4095
|
+
`);
|
|
4096
|
+
} else {
|
|
4097
|
+
wrappedStreamCallback(`
|
|
4098
|
+
|
|
4099
|
+
\u{1F504} **Retrying ${externalTool.name} (attempt ${attempts}/${MAX_TOOL_ATTEMPTS})...**
|
|
4100
|
+
|
|
4101
|
+
`);
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
const result2 = await externalTool.fn(toolInput);
|
|
4105
|
+
logger.info(`[${this.getProviderName()}] External tool ${externalTool.name} executed successfully`);
|
|
4106
|
+
logCollector?.info(`\u2713 ${externalTool.name} executed successfully`);
|
|
4107
|
+
if (wrappedStreamCallback) {
|
|
4108
|
+
wrappedStreamCallback(`\u2705 **${externalTool.name} completed successfully**
|
|
4109
|
+
|
|
4110
|
+
`);
|
|
4111
|
+
}
|
|
4112
|
+
return JSON.stringify(result2, null, 2);
|
|
4113
|
+
} catch (error) {
|
|
4114
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4115
|
+
logger.error(`[${this.getProviderName()}] External tool ${externalTool.name} failed (attempt ${attempts}/${MAX_TOOL_ATTEMPTS}): ${errorMsg}`);
|
|
4116
|
+
logCollector?.error(`\u2717 ${externalTool.name} failed: ${errorMsg}`);
|
|
4117
|
+
if (wrappedStreamCallback) {
|
|
4118
|
+
wrappedStreamCallback(`\u274C **${externalTool.name} failed:**
|
|
4119
|
+
\`\`\`
|
|
4120
|
+
${errorMsg}
|
|
4121
|
+
\`\`\`
|
|
4122
|
+
|
|
4123
|
+
`);
|
|
4124
|
+
if (attempts < MAX_TOOL_ATTEMPTS) {
|
|
4125
|
+
wrappedStreamCallback(`\u{1F527} **Retrying with adjusted parameters...**
|
|
4126
|
+
|
|
4127
|
+
`);
|
|
4128
|
+
}
|
|
4129
|
+
}
|
|
4130
|
+
throw new Error(`Tool execution failed: ${errorMsg}`);
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
4339
4134
|
}
|
|
4340
|
-
throw new Error(`Unknown tool: ${toolName}`);
|
|
4341
4135
|
};
|
|
4342
4136
|
const result = await LLM.streamWithTools(
|
|
4343
4137
|
{
|
|
@@ -4354,8 +4148,8 @@ ${errorMsg}
|
|
|
4354
4148
|
partial: wrappedStreamCallback
|
|
4355
4149
|
// Pass the wrapped streaming callback to LLM
|
|
4356
4150
|
},
|
|
4357
|
-
|
|
4358
|
-
// max iterations: allows for 6 retries + final response + buffer
|
|
4151
|
+
20
|
|
4152
|
+
// max iterations: allows for 6 query retries + 3 tool retries + final response + buffer
|
|
4359
4153
|
);
|
|
4360
4154
|
logger.info(`[${this.getProviderName()}] Text response stream completed`);
|
|
4361
4155
|
const textResponse = fullStreamedText || result || "I apologize, but I was unable to generate a response.";
|
|
@@ -4410,24 +4204,22 @@ ${errorMsg}
|
|
|
4410
4204
|
}
|
|
4411
4205
|
let container_componet = null;
|
|
4412
4206
|
if (matchedComponents.length > 0) {
|
|
4207
|
+
logger.info(`[${this.getProviderName()}] Created MultiComponentContainer: "${layoutTitle}" with ${matchedComponents.length} components and ${actions.length} actions`);
|
|
4208
|
+
logCollector?.info(`Created dashboard: "${layoutTitle}" with ${matchedComponents.length} components and ${actions.length} actions`);
|
|
4413
4209
|
container_componet = {
|
|
4414
|
-
id: `
|
|
4210
|
+
id: `container_${Date.now()}`,
|
|
4415
4211
|
name: "MultiComponentContainer",
|
|
4416
4212
|
type: "Container",
|
|
4417
4213
|
description: layoutDescription,
|
|
4418
|
-
category: "dynamic",
|
|
4419
|
-
keywords: ["dashboard", "layout", "container"],
|
|
4420
4214
|
props: {
|
|
4421
4215
|
config: {
|
|
4422
|
-
components: matchedComponents,
|
|
4423
4216
|
title: layoutTitle,
|
|
4424
|
-
description: layoutDescription
|
|
4217
|
+
description: layoutDescription,
|
|
4218
|
+
components: matchedComponents
|
|
4425
4219
|
},
|
|
4426
4220
|
actions
|
|
4427
4221
|
}
|
|
4428
4222
|
};
|
|
4429
|
-
logger.info(`[${this.getProviderName()}] Created MultiComponentContainer: "${layoutTitle}" with ${matchedComponents.length} components and ${actions.length} actions`);
|
|
4430
|
-
logCollector?.info(`Created dashboard: "${layoutTitle}" with ${matchedComponents.length} components and ${actions.length} actions`);
|
|
4431
4223
|
}
|
|
4432
4224
|
return {
|
|
4433
4225
|
success: true,
|
|
@@ -4458,201 +4250,134 @@ ${errorMsg}
|
|
|
4458
4250
|
}
|
|
4459
4251
|
}
|
|
4460
4252
|
/**
|
|
4461
|
-
*
|
|
4462
|
-
*
|
|
4463
|
-
*
|
|
4253
|
+
* Main orchestration function with semantic search and multi-step classification
|
|
4254
|
+
* NEW FLOW (Recommended):
|
|
4255
|
+
* 1. Semantic search: Check previous conversations (>60% match)
|
|
4256
|
+
* - If match found → Adapt UI block parameters and return
|
|
4257
|
+
* 2. Category classification: Determine if data_analysis, requires_external_tools, or text_response
|
|
4258
|
+
* 3. Route appropriately based on category and response mode
|
|
4259
|
+
*
|
|
4260
|
+
* @param responseMode - 'component' for component generation (default), 'text' for text responses
|
|
4261
|
+
* @param streamCallback - Optional callback function to receive text chunks as they stream (only for text mode)
|
|
4262
|
+
* @param collections - Collection registry for executing database queries (required for text mode)
|
|
4263
|
+
* @param externalTools - Optional array of external tools (email, calendar, etc.) that can be called (only for text mode)
|
|
4464
4264
|
*/
|
|
4465
|
-
async
|
|
4466
|
-
const
|
|
4265
|
+
async handleUserRequest(userPrompt, components, apiKey, logCollector, conversationHistory, responseMode = "text", streamCallback, collections, externalTools, userId) {
|
|
4266
|
+
const startTime = Date.now();
|
|
4267
|
+
logger.info(`[${this.getProviderName()}] handleUserRequest called with responseMode: ${responseMode}`);
|
|
4268
|
+
logCollector?.info(`Starting request processing with mode: ${responseMode}`);
|
|
4467
4269
|
try {
|
|
4468
|
-
logger.info(`[${this.getProviderName()}]
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
});
|
|
4489
|
-
|
|
4490
|
-
const
|
|
4491
|
-
for (const settledResult of settledResults) {
|
|
4492
|
-
if (settledResult.status === "fulfilled") {
|
|
4493
|
-
const { vizType, result } = settledResult.value;
|
|
4494
|
-
if (result.component) {
|
|
4495
|
-
matchedComponents.push(result.component);
|
|
4496
|
-
logCollector?.info(`Matched: ${result.component.name}`);
|
|
4497
|
-
logger.info("Component : ", result.component.name, " props: ", result.component.props);
|
|
4498
|
-
} else {
|
|
4499
|
-
logCollector?.warn(`Failed to match component for type: ${vizType}`);
|
|
4500
|
-
}
|
|
4501
|
-
} else {
|
|
4502
|
-
logCollector?.warn(`Error matching component: ${settledResult.reason?.message || "Unknown error"}`);
|
|
4503
|
-
}
|
|
4504
|
-
}
|
|
4505
|
-
logger.debug(`[${this.getProviderName()}] Matched ${matchedComponents.length} components for multi-component container`);
|
|
4506
|
-
if (matchedComponents.length === 0) {
|
|
4507
|
-
return {
|
|
4508
|
-
success: true,
|
|
4509
|
-
data: {
|
|
4510
|
-
component: null,
|
|
4511
|
-
reasoning: "Failed to match any components for the requested visualization types",
|
|
4512
|
-
method: "classification-multi-failed",
|
|
4513
|
-
questionType: classification.questionType,
|
|
4514
|
-
needsMultipleComponents: true,
|
|
4515
|
-
propsModified: false,
|
|
4516
|
-
queryModified: false
|
|
4517
|
-
},
|
|
4518
|
-
errors: []
|
|
4519
|
-
};
|
|
4520
|
-
}
|
|
4521
|
-
logCollector?.info("Generating container metadata...");
|
|
4522
|
-
const containerMetadata = await this.generateContainerMetadata(
|
|
4523
|
-
userPrompt,
|
|
4524
|
-
classification.visualizations,
|
|
4525
|
-
apiKey,
|
|
4526
|
-
logCollector,
|
|
4527
|
-
conversationHistory
|
|
4528
|
-
);
|
|
4529
|
-
const containerComponent = {
|
|
4530
|
-
id: `multi_container_${Date.now()}`,
|
|
4531
|
-
name: "MultiComponentContainer",
|
|
4532
|
-
type: "Container",
|
|
4533
|
-
description: containerMetadata.description,
|
|
4534
|
-
category: "dynamic",
|
|
4535
|
-
keywords: ["multi", "container", "dashboard"],
|
|
4536
|
-
props: {
|
|
4537
|
-
config: {
|
|
4538
|
-
components: matchedComponents,
|
|
4539
|
-
layout: "grid",
|
|
4540
|
-
spacing: 24,
|
|
4541
|
-
title: containerMetadata.title,
|
|
4542
|
-
description: containerMetadata.description
|
|
4543
|
-
}
|
|
4544
|
-
}
|
|
4545
|
-
};
|
|
4546
|
-
logCollector?.info(`Created multi-component container with ${matchedComponents.length} components: "${containerMetadata.title}"`);
|
|
4270
|
+
logger.info(`[${this.getProviderName()}] Step 1: Searching previous conversations...`);
|
|
4271
|
+
logCollector?.info("Step 1: Searching for similar previous conversations...");
|
|
4272
|
+
const conversationMatch = await conversation_search_default.searchConversations({
|
|
4273
|
+
userPrompt,
|
|
4274
|
+
collections,
|
|
4275
|
+
userId,
|
|
4276
|
+
similarityThreshold: 0.6
|
|
4277
|
+
// 60% threshold
|
|
4278
|
+
});
|
|
4279
|
+
logger.info("conversationMatch:", conversationMatch);
|
|
4280
|
+
if (conversationMatch) {
|
|
4281
|
+
logger.info(
|
|
4282
|
+
`[${this.getProviderName()}] \u2713 Found matching conversation with ${(conversationMatch.similarity * 100).toFixed(2)}% similarity`
|
|
4283
|
+
);
|
|
4284
|
+
logCollector?.info(
|
|
4285
|
+
`\u2713 Found similar conversation (${(conversationMatch.similarity * 100).toFixed(2)}% match)`
|
|
4286
|
+
);
|
|
4287
|
+
if (conversationMatch.similarity >= 0.99) {
|
|
4288
|
+
const elapsedTime2 = Date.now() - startTime;
|
|
4289
|
+
logger.info(`[${this.getProviderName()}] \u2713 100% match - returning UI block directly without adaptation`);
|
|
4290
|
+
logCollector?.info(`\u2713 Exact match (${(conversationMatch.similarity * 100).toFixed(2)}%) - returning cached result`);
|
|
4291
|
+
logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
4292
|
+
const component = conversationMatch.uiBlock?.generatedComponentMetadata || conversationMatch.uiBlock?.component;
|
|
4547
4293
|
return {
|
|
4548
4294
|
success: true,
|
|
4549
4295
|
data: {
|
|
4550
|
-
component
|
|
4551
|
-
reasoning: `
|
|
4552
|
-
method:
|
|
4553
|
-
|
|
4554
|
-
needsMultipleComponents: true,
|
|
4555
|
-
propsModified: false,
|
|
4556
|
-
queryModified: false
|
|
4296
|
+
component,
|
|
4297
|
+
reasoning: `Exact match from previous conversation (${(conversationMatch.similarity * 100).toFixed(2)}% similarity)`,
|
|
4298
|
+
method: `${this.getProviderName()}-semantic-match-exact`,
|
|
4299
|
+
semanticSimilarity: conversationMatch.similarity
|
|
4557
4300
|
},
|
|
4558
4301
|
errors: []
|
|
4559
4302
|
};
|
|
4560
|
-
}
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4303
|
+
}
|
|
4304
|
+
logCollector?.info(`Adapting parameters for similar question...`);
|
|
4305
|
+
const originalPrompt = conversationMatch.metadata?.userPrompt || "Previous question";
|
|
4306
|
+
const adaptResult = await this.adaptUIBlockParameters(
|
|
4307
|
+
userPrompt,
|
|
4308
|
+
originalPrompt,
|
|
4309
|
+
conversationMatch.uiBlock,
|
|
4310
|
+
apiKey,
|
|
4311
|
+
logCollector
|
|
4312
|
+
);
|
|
4313
|
+
if (adaptResult.success && adaptResult.adaptedComponent) {
|
|
4314
|
+
const elapsedTime2 = Date.now() - startTime;
|
|
4315
|
+
logger.info(`[${this.getProviderName()}] \u2713 Successfully adapted UI block parameters`);
|
|
4316
|
+
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
4317
|
+
logCollector?.info(`\u2713 UI block adapted successfully`);
|
|
4318
|
+
logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
4564
4319
|
return {
|
|
4565
4320
|
success: true,
|
|
4566
4321
|
data: {
|
|
4567
|
-
component:
|
|
4568
|
-
reasoning:
|
|
4569
|
-
method:
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
propsModified: false,
|
|
4573
|
-
queryModified: false
|
|
4322
|
+
component: adaptResult.adaptedComponent,
|
|
4323
|
+
reasoning: `Adapted from previous conversation: ${originalPrompt}`,
|
|
4324
|
+
method: `${this.getProviderName()}-semantic-match`,
|
|
4325
|
+
semanticSimilarity: conversationMatch.similarity,
|
|
4326
|
+
parametersChanged: adaptResult.parametersChanged
|
|
4574
4327
|
},
|
|
4575
4328
|
errors: []
|
|
4576
4329
|
};
|
|
4577
4330
|
} else {
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
return {
|
|
4581
|
-
success: true,
|
|
4582
|
-
data: {
|
|
4583
|
-
component: result.component,
|
|
4584
|
-
reasoning: result.reasoning,
|
|
4585
|
-
method: "classification-generated-auto",
|
|
4586
|
-
questionType: classification.questionType,
|
|
4587
|
-
needsMultipleComponents: false,
|
|
4588
|
-
propsModified: false,
|
|
4589
|
-
queryModified: false
|
|
4590
|
-
},
|
|
4591
|
-
errors: []
|
|
4592
|
-
};
|
|
4331
|
+
logger.info(`[${this.getProviderName()}] Could not adapt matched conversation, continuing to category classification`);
|
|
4332
|
+
logCollector?.warn(`Could not adapt matched conversation: ${adaptResult.explanation}`);
|
|
4593
4333
|
}
|
|
4594
|
-
} else if (classification.questionType === "data_modification" || classification.questionType === "general") {
|
|
4595
|
-
const matchMsg = "Using component matching for data modification...";
|
|
4596
|
-
logCollector?.info(matchMsg);
|
|
4597
|
-
const matchResult = await this.matchComponent(userPrompt, components, apiKey, logCollector, conversationHistory);
|
|
4598
|
-
return {
|
|
4599
|
-
success: true,
|
|
4600
|
-
data: {
|
|
4601
|
-
component: matchResult.component,
|
|
4602
|
-
reasoning: matchResult.reasoning,
|
|
4603
|
-
method: "classification-matched",
|
|
4604
|
-
questionType: classification.questionType,
|
|
4605
|
-
needsMultipleComponents: false,
|
|
4606
|
-
propsModified: matchResult.propsModified,
|
|
4607
|
-
queryModified: matchResult.queryModified
|
|
4608
|
-
},
|
|
4609
|
-
errors: []
|
|
4610
|
-
};
|
|
4611
4334
|
} else {
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
}
|
|
4335
|
+
logger.info(`[${this.getProviderName()}] No matching previous conversations found, proceeding to category classification`);
|
|
4336
|
+
logCollector?.info("No similar previous conversations found. Proceeding to category classification...");
|
|
4337
|
+
}
|
|
4338
|
+
logger.info(`[${this.getProviderName()}] Step 2: Classifying question category...`);
|
|
4339
|
+
logCollector?.info("Step 2: Classifying question category...");
|
|
4340
|
+
const categoryClassification = await this.classifyQuestionCategory(
|
|
4341
|
+
userPrompt,
|
|
4342
|
+
apiKey,
|
|
4343
|
+
logCollector,
|
|
4344
|
+
conversationHistory,
|
|
4345
|
+
externalTools
|
|
4346
|
+
);
|
|
4347
|
+
logger.info(
|
|
4348
|
+
`[${this.getProviderName()}] Question classified as: ${categoryClassification.category} (confidence: ${categoryClassification.confidence}%)`
|
|
4349
|
+
);
|
|
4350
|
+
logCollector?.info(
|
|
4351
|
+
`Category: ${categoryClassification.category} | Confidence: ${categoryClassification.confidence}%`
|
|
4352
|
+
);
|
|
4353
|
+
let toolsToUse = [];
|
|
4354
|
+
if (categoryClassification.externalTools && categoryClassification.externalTools.length > 0) {
|
|
4355
|
+
logger.info(`[${this.getProviderName()}] Identified ${categoryClassification.externalTools.length} external tools needed`);
|
|
4356
|
+
logCollector?.info(`Identified external tools: ${categoryClassification.externalTools.map((t) => t.name || t.type).join(", ")}`);
|
|
4357
|
+
toolsToUse = categoryClassification.externalTools?.map((t) => ({
|
|
4358
|
+
id: t.type,
|
|
4359
|
+
name: t.name,
|
|
4360
|
+
description: t.description,
|
|
4361
|
+
params: t.parameters || {},
|
|
4362
|
+
fn: (() => {
|
|
4363
|
+
const realTool = externalTools?.find((tool) => tool.id === t.type);
|
|
4364
|
+
if (realTool) {
|
|
4365
|
+
logger.info(`[${this.getProviderName()}] Using real tool implementation for ${t.type}`);
|
|
4366
|
+
return realTool.fn;
|
|
4367
|
+
} else {
|
|
4368
|
+
logger.warn(`[${this.getProviderName()}] Tool ${t.type} not found in registered tools`);
|
|
4369
|
+
return async () => ({ success: false, message: `Tool ${t.name || t.type} not registered` });
|
|
4370
|
+
}
|
|
4371
|
+
})()
|
|
4372
|
+
})) || [];
|
|
4373
|
+
}
|
|
4374
|
+
if (categoryClassification.category === "data_analysis") {
|
|
4375
|
+
logger.info(`[${this.getProviderName()}] Routing to data analysis (SELECT operations)`);
|
|
4376
|
+
logCollector?.info("Routing to data analysis...");
|
|
4377
|
+
} else if (categoryClassification.category === "data_modification") {
|
|
4378
|
+
logger.info(`[${this.getProviderName()}] Routing to data modification (INSERT/UPDATE/DELETE operations)`);
|
|
4379
|
+
logCollector?.info("Routing to data modification...");
|
|
4626
4380
|
}
|
|
4627
|
-
} catch (error) {
|
|
4628
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4629
|
-
logger.error(`[${this.getProviderName()}] Error generating component response: ${errorMsg}`);
|
|
4630
|
-
logger.debug(`[${this.getProviderName()}] Component response generation error details:`, error);
|
|
4631
|
-
logCollector?.error(`Error generating component response: ${errorMsg}`);
|
|
4632
|
-
errors.push(errorMsg);
|
|
4633
|
-
return {
|
|
4634
|
-
success: false,
|
|
4635
|
-
errors,
|
|
4636
|
-
data: void 0
|
|
4637
|
-
};
|
|
4638
|
-
}
|
|
4639
|
-
}
|
|
4640
|
-
/**
|
|
4641
|
-
* Main orchestration function that classifies question and routes to appropriate handler
|
|
4642
|
-
* This is the NEW recommended entry point for handling user requests
|
|
4643
|
-
* Supports both component generation and text response modes
|
|
4644
|
-
*
|
|
4645
|
-
* @param responseMode - 'component' for component generation (default), 'text' for text responses
|
|
4646
|
-
* @param streamCallback - Optional callback function to receive text chunks as they stream (only for text mode)
|
|
4647
|
-
* @param collections - Collection registry for executing database queries (required for text mode)
|
|
4648
|
-
* @param externalTools - Optional array of external tools (email, calendar, etc.) that can be called (only for text mode)
|
|
4649
|
-
*/
|
|
4650
|
-
async handleUserRequest(userPrompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools) {
|
|
4651
|
-
const startTime = Date.now();
|
|
4652
|
-
logger.info(`[${this.getProviderName()}] handleUserRequest called with responseMode: ${responseMode}`);
|
|
4653
|
-
if (responseMode === "text") {
|
|
4654
|
-
logger.info(`[${this.getProviderName()}] Using text response mode`);
|
|
4655
|
-
logCollector?.info("Generating text response...");
|
|
4656
4381
|
const textResponse = await this.generateTextResponse(
|
|
4657
4382
|
userPrompt,
|
|
4658
4383
|
apiKey,
|
|
@@ -4661,40 +4386,29 @@ ${errorMsg}
|
|
|
4661
4386
|
streamCallback,
|
|
4662
4387
|
collections,
|
|
4663
4388
|
components,
|
|
4664
|
-
|
|
4389
|
+
toolsToUse
|
|
4665
4390
|
);
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime3}ms (${(elapsedTime3 / 1e3).toFixed(2)}s)`);
|
|
4670
|
-
logCollector?.info(`Total time taken: ${elapsedTime3}ms (${(elapsedTime3 / 1e3).toFixed(2)}s)`);
|
|
4671
|
-
return textResponse;
|
|
4672
|
-
}
|
|
4673
|
-
const elapsedTime2 = Date.now() - startTime;
|
|
4674
|
-
logger.info(`[${this.getProviderName()}] Text response generated successfully`);
|
|
4675
|
-
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
4676
|
-
logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
4391
|
+
const elapsedTime = Date.now() - startTime;
|
|
4392
|
+
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
4393
|
+
logCollector?.info(`Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
4677
4394
|
return textResponse;
|
|
4395
|
+
} catch (error) {
|
|
4396
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4397
|
+
logger.error(`[${this.getProviderName()}] Error in handleUserRequest: ${errorMsg}`);
|
|
4398
|
+
logger.debug(`[${this.getProviderName()}] Error details:`, error);
|
|
4399
|
+
logCollector?.error(`Error processing request: ${errorMsg}`);
|
|
4400
|
+
const elapsedTime = Date.now() - startTime;
|
|
4401
|
+
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
4402
|
+
logCollector?.info(`Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
4403
|
+
return {
|
|
4404
|
+
success: false,
|
|
4405
|
+
errors: [errorMsg],
|
|
4406
|
+
data: {
|
|
4407
|
+
text: "I apologize, but I encountered an error processing your request. Please try again.",
|
|
4408
|
+
method: `${this.getProviderName()}-orchestration-error`
|
|
4409
|
+
}
|
|
4410
|
+
};
|
|
4678
4411
|
}
|
|
4679
|
-
const componentResponse = await this.generateComponentResponse(
|
|
4680
|
-
userPrompt,
|
|
4681
|
-
components,
|
|
4682
|
-
apiKey,
|
|
4683
|
-
logCollector,
|
|
4684
|
-
conversationHistory
|
|
4685
|
-
);
|
|
4686
|
-
if (!componentResponse.success) {
|
|
4687
|
-
const elapsedTime2 = Date.now() - startTime;
|
|
4688
|
-
logger.error(`[${this.getProviderName()}] Component response generation failed`);
|
|
4689
|
-
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
4690
|
-
logCollector?.info(`Total time taken: ${elapsedTime2}ms (${(elapsedTime2 / 1e3).toFixed(2)}s)`);
|
|
4691
|
-
return componentResponse;
|
|
4692
|
-
}
|
|
4693
|
-
const elapsedTime = Date.now() - startTime;
|
|
4694
|
-
logger.info(`[${this.getProviderName()}] Component response generated successfully`);
|
|
4695
|
-
logger.info(`[${this.getProviderName()}] Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
4696
|
-
logCollector?.info(`Total time taken: ${elapsedTime}ms (${(elapsedTime / 1e3).toFixed(2)}s)`);
|
|
4697
|
-
return componentResponse;
|
|
4698
4412
|
}
|
|
4699
4413
|
/**
|
|
4700
4414
|
* Generate next questions that the user might ask based on the original prompt and generated component
|
|
@@ -4807,7 +4521,7 @@ function getLLMProviders() {
|
|
|
4807
4521
|
return DEFAULT_PROVIDERS;
|
|
4808
4522
|
}
|
|
4809
4523
|
}
|
|
4810
|
-
var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools) => {
|
|
4524
|
+
var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
4811
4525
|
logger.debug("[useAnthropicMethod] Initializing Anthropic Claude matching method");
|
|
4812
4526
|
logger.debug(`[useAnthropicMethod] Response mode: ${responseMode}`);
|
|
4813
4527
|
const msg = `Using Anthropic Claude ${responseMode === "text" ? "text response" : "matching"} method...`;
|
|
@@ -4819,11 +4533,11 @@ var useAnthropicMethod = async (prompt, components, apiKey, logCollector, conver
|
|
|
4819
4533
|
return { success: false, errors: [emptyMsg] };
|
|
4820
4534
|
}
|
|
4821
4535
|
logger.debug(`[useAnthropicMethod] Processing with ${components.length} components`);
|
|
4822
|
-
const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools);
|
|
4536
|
+
const matchResult = await anthropicLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
4823
4537
|
logger.info(`[useAnthropicMethod] Successfully generated ${responseMode} using Anthropic`);
|
|
4824
4538
|
return matchResult;
|
|
4825
4539
|
};
|
|
4826
|
-
var useGroqMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools) => {
|
|
4540
|
+
var useGroqMethod = async (prompt, components, apiKey, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
4827
4541
|
logger.debug("[useGroqMethod] Initializing Groq LLM matching method");
|
|
4828
4542
|
logger.debug(`[useGroqMethod] Response mode: ${responseMode}`);
|
|
4829
4543
|
const msg = `Using Groq LLM ${responseMode === "text" ? "text response" : "matching"} method...`;
|
|
@@ -4836,14 +4550,14 @@ var useGroqMethod = async (prompt, components, apiKey, logCollector, conversatio
|
|
|
4836
4550
|
return { success: false, errors: [emptyMsg] };
|
|
4837
4551
|
}
|
|
4838
4552
|
logger.debug(`[useGroqMethod] Processing with ${components.length} components`);
|
|
4839
|
-
const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools);
|
|
4553
|
+
const matchResult = await groqLLM.handleUserRequest(prompt, components, apiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
4840
4554
|
logger.info(`[useGroqMethod] Successfully generated ${responseMode} using Groq`);
|
|
4841
4555
|
return matchResult;
|
|
4842
4556
|
};
|
|
4843
4557
|
var getUserResponseFromCache = async (prompt) => {
|
|
4844
4558
|
return false;
|
|
4845
4559
|
};
|
|
4846
|
-
var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools) => {
|
|
4560
|
+
var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey, llmProviders, logCollector, conversationHistory, responseMode = "component", streamCallback, collections, externalTools, userId) => {
|
|
4847
4561
|
logger.debug(`[get_user_response] Starting user response generation for prompt: "${prompt.substring(0, 50)}..."`);
|
|
4848
4562
|
logger.debug(`[get_user_response] Response mode: ${responseMode}`);
|
|
4849
4563
|
logger.debug("[get_user_response] Checking cache for existing response");
|
|
@@ -4876,9 +4590,9 @@ var get_user_response = async (prompt, components, anthropicApiKey, groqApiKey,
|
|
|
4876
4590
|
logCollector?.info(attemptMsg);
|
|
4877
4591
|
let result;
|
|
4878
4592
|
if (provider === "anthropic") {
|
|
4879
|
-
result = await useAnthropicMethod(prompt, components, anthropicApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools);
|
|
4593
|
+
result = await useAnthropicMethod(prompt, components, anthropicApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
4880
4594
|
} else if (provider === "groq") {
|
|
4881
|
-
result = await useGroqMethod(prompt, components, groqApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools);
|
|
4595
|
+
result = await useGroqMethod(prompt, components, groqApiKey, logCollector, conversationHistory, responseMode, streamCallback, collections, externalTools, userId);
|
|
4882
4596
|
} else {
|
|
4883
4597
|
logger.warn(`[get_user_response] Unknown provider: ${provider} - skipping`);
|
|
4884
4598
|
errors.push(`Unknown provider: ${provider}`);
|
|
@@ -5095,6 +4809,111 @@ var UILogCollector = class {
|
|
|
5095
4809
|
}
|
|
5096
4810
|
};
|
|
5097
4811
|
|
|
4812
|
+
// src/utils/conversation-saver.ts
|
|
4813
|
+
async function saveConversation(params) {
|
|
4814
|
+
const { userId, userPrompt, uiblock, uiBlockId, threadId, collections } = params;
|
|
4815
|
+
if (!userId) {
|
|
4816
|
+
logger.warn("[CONVERSATION_SAVER] Skipping save: userId not provided");
|
|
4817
|
+
return {
|
|
4818
|
+
success: false,
|
|
4819
|
+
error: "userId is required"
|
|
4820
|
+
};
|
|
4821
|
+
}
|
|
4822
|
+
if (!userPrompt) {
|
|
4823
|
+
logger.warn("[CONVERSATION_SAVER] Skipping save: userPrompt not provided");
|
|
4824
|
+
return {
|
|
4825
|
+
success: false,
|
|
4826
|
+
error: "userPrompt is required"
|
|
4827
|
+
};
|
|
4828
|
+
}
|
|
4829
|
+
if (!uiblock) {
|
|
4830
|
+
logger.warn("[CONVERSATION_SAVER] Skipping save: uiblock not provided");
|
|
4831
|
+
return {
|
|
4832
|
+
success: false,
|
|
4833
|
+
error: "uiblock is required"
|
|
4834
|
+
};
|
|
4835
|
+
}
|
|
4836
|
+
if (!threadId) {
|
|
4837
|
+
logger.warn("[CONVERSATION_SAVER] Skipping save: threadId not provided");
|
|
4838
|
+
return {
|
|
4839
|
+
success: false,
|
|
4840
|
+
error: "threadId is required"
|
|
4841
|
+
};
|
|
4842
|
+
}
|
|
4843
|
+
if (!uiBlockId) {
|
|
4844
|
+
logger.warn("[CONVERSATION_SAVER] Skipping save: uiBlockId not provided");
|
|
4845
|
+
return {
|
|
4846
|
+
success: false,
|
|
4847
|
+
error: "uiBlockId is required"
|
|
4848
|
+
};
|
|
4849
|
+
}
|
|
4850
|
+
if (!collections?.["user-conversations"]?.["create"]) {
|
|
4851
|
+
logger.debug('[CONVERSATION_SAVER] Collection "user-conversations.create" not available, skipping save');
|
|
4852
|
+
return {
|
|
4853
|
+
success: false,
|
|
4854
|
+
error: "user-conversations.create collection not available"
|
|
4855
|
+
};
|
|
4856
|
+
}
|
|
4857
|
+
try {
|
|
4858
|
+
logger.info(`[CONVERSATION_SAVER] Saving conversation for userId: ${userId}, uiBlockId: ${uiBlockId}, threadId: ${threadId}`);
|
|
4859
|
+
const userIdNumber = Number(userId);
|
|
4860
|
+
if (isNaN(userIdNumber)) {
|
|
4861
|
+
logger.warn(`[CONVERSATION_SAVER] Invalid userId: ${userId} (not a valid number)`);
|
|
4862
|
+
return {
|
|
4863
|
+
success: false,
|
|
4864
|
+
error: `Invalid userId: ${userId} (not a valid number)`
|
|
4865
|
+
};
|
|
4866
|
+
}
|
|
4867
|
+
const saveResult = await collections["user-conversations"]["create"]({
|
|
4868
|
+
userId: userIdNumber,
|
|
4869
|
+
userPrompt,
|
|
4870
|
+
uiblock,
|
|
4871
|
+
threadId
|
|
4872
|
+
});
|
|
4873
|
+
if (!saveResult?.success) {
|
|
4874
|
+
logger.warn(`[CONVERSATION_SAVER] Failed to save conversation to PostgreSQL: ${saveResult?.message || "Unknown error"}`);
|
|
4875
|
+
return {
|
|
4876
|
+
success: false,
|
|
4877
|
+
error: saveResult?.message || "Unknown error from backend"
|
|
4878
|
+
};
|
|
4879
|
+
}
|
|
4880
|
+
logger.info(`[CONVERSATION_SAVER] Successfully saved conversation to PostgreSQL, id: ${saveResult.data?.id}`);
|
|
4881
|
+
if (collections?.["conversation-history"]?.["embed"]) {
|
|
4882
|
+
try {
|
|
4883
|
+
logger.info("[CONVERSATION_SAVER] Creating embedding for semantic search...");
|
|
4884
|
+
const embedResult = await collections["conversation-history"]["embed"]({
|
|
4885
|
+
uiBlockId,
|
|
4886
|
+
userPrompt,
|
|
4887
|
+
uiBlock: uiblock,
|
|
4888
|
+
userId: userIdNumber
|
|
4889
|
+
});
|
|
4890
|
+
if (embedResult?.success) {
|
|
4891
|
+
logger.info("[CONVERSATION_SAVER] Successfully created embedding");
|
|
4892
|
+
} else {
|
|
4893
|
+
logger.warn("[CONVERSATION_SAVER] Failed to create embedding:", embedResult?.error || "Unknown error");
|
|
4894
|
+
}
|
|
4895
|
+
} catch (embedError) {
|
|
4896
|
+
const embedErrorMsg = embedError instanceof Error ? embedError.message : String(embedError);
|
|
4897
|
+
logger.warn("[CONVERSATION_SAVER] Error creating embedding:", embedErrorMsg);
|
|
4898
|
+
}
|
|
4899
|
+
} else {
|
|
4900
|
+
logger.debug("[CONVERSATION_SAVER] Embedding collection not available, skipping ChromaDB storage");
|
|
4901
|
+
}
|
|
4902
|
+
return {
|
|
4903
|
+
success: true,
|
|
4904
|
+
conversationId: saveResult.data?.id,
|
|
4905
|
+
message: "Conversation saved successfully"
|
|
4906
|
+
};
|
|
4907
|
+
} catch (error) {
|
|
4908
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4909
|
+
logger.error("[CONVERSATION_SAVER] Error saving conversation:", errorMessage);
|
|
4910
|
+
return {
|
|
4911
|
+
success: false,
|
|
4912
|
+
error: errorMessage
|
|
4913
|
+
};
|
|
4914
|
+
}
|
|
4915
|
+
}
|
|
4916
|
+
|
|
5098
4917
|
// src/config/context.ts
|
|
5099
4918
|
var CONTEXT_CONFIG = {
|
|
5100
4919
|
/**
|
|
@@ -5106,7 +4925,7 @@ var CONTEXT_CONFIG = {
|
|
|
5106
4925
|
};
|
|
5107
4926
|
|
|
5108
4927
|
// src/handlers/user-prompt-request.ts
|
|
5109
|
-
var get_user_request = async (data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections, externalTools) => {
|
|
4928
|
+
var get_user_request = async (data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections, externalTools, userId) => {
|
|
5110
4929
|
const errors = [];
|
|
5111
4930
|
logger.debug("[USER_PROMPT_REQ] Parsing incoming message data");
|
|
5112
4931
|
const parseResult = UserPromptRequestMessageSchema.safeParse(data);
|
|
@@ -5187,7 +5006,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
5187
5006
|
responseMode,
|
|
5188
5007
|
streamCallback,
|
|
5189
5008
|
collections,
|
|
5190
|
-
externalTools
|
|
5009
|
+
externalTools,
|
|
5010
|
+
userId
|
|
5191
5011
|
);
|
|
5192
5012
|
logCollector.info("User prompt request completed");
|
|
5193
5013
|
const uiBlockId = existingUiBlockId;
|
|
@@ -5238,6 +5058,34 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
5238
5058
|
}
|
|
5239
5059
|
thread.addUIBlock(uiBlock);
|
|
5240
5060
|
logger.info(`Created UIBlock: ${uiBlockId} in Thread: ${threadId}`);
|
|
5061
|
+
if (userId) {
|
|
5062
|
+
const responseMethod = userResponse.data?.method || "";
|
|
5063
|
+
const semanticSimilarity = userResponse.data?.semanticSimilarity || 0;
|
|
5064
|
+
const isExactMatch = responseMethod.includes("semantic-match") && semanticSimilarity >= 0.99;
|
|
5065
|
+
if (isExactMatch) {
|
|
5066
|
+
logger.info(
|
|
5067
|
+
`Skipping conversation save - response from exact semantic match (${(semanticSimilarity * 100).toFixed(2)}% similarity)`
|
|
5068
|
+
);
|
|
5069
|
+
logCollector.info(
|
|
5070
|
+
`Using exact cached result (${(semanticSimilarity * 100).toFixed(2)}% match) - not saving duplicate conversation`
|
|
5071
|
+
);
|
|
5072
|
+
} else {
|
|
5073
|
+
const uiBlockData = uiBlock.toJSON();
|
|
5074
|
+
const saveResult = await saveConversation({
|
|
5075
|
+
userId,
|
|
5076
|
+
userPrompt: prompt,
|
|
5077
|
+
uiblock: uiBlockData,
|
|
5078
|
+
uiBlockId,
|
|
5079
|
+
threadId,
|
|
5080
|
+
collections
|
|
5081
|
+
});
|
|
5082
|
+
if (saveResult.success) {
|
|
5083
|
+
logger.info(`Conversation saved with ID: ${saveResult.conversationId}`);
|
|
5084
|
+
} else {
|
|
5085
|
+
logger.warn(`Failed to save conversation: ${saveResult.error}`);
|
|
5086
|
+
}
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5241
5089
|
return {
|
|
5242
5090
|
success: userResponse.success,
|
|
5243
5091
|
data: userResponse.data,
|
|
@@ -5248,8 +5096,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
5248
5096
|
wsId
|
|
5249
5097
|
};
|
|
5250
5098
|
};
|
|
5251
|
-
async function handleUserPromptRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections, externalTools) {
|
|
5252
|
-
const response = await get_user_request(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections, externalTools);
|
|
5099
|
+
async function handleUserPromptRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections, externalTools, userId) {
|
|
5100
|
+
const response = await get_user_request(data, components, sendMessage, anthropicApiKey, groqApiKey, llmProviders, collections, externalTools, userId);
|
|
5253
5101
|
sendDataResponse4(
|
|
5254
5102
|
response.id || data.id,
|
|
5255
5103
|
{
|
|
@@ -7479,7 +7327,7 @@ var SuperatomSDK = class {
|
|
|
7479
7327
|
});
|
|
7480
7328
|
break;
|
|
7481
7329
|
case "USER_PROMPT_REQ":
|
|
7482
|
-
handleUserPromptRequest(parsed, this.components, (msg) => this.send(msg), this.anthropicApiKey, this.groqApiKey, this.llmProviders, this.collections, this.tools).catch((error) => {
|
|
7330
|
+
handleUserPromptRequest(parsed, this.components, (msg) => this.send(msg), this.anthropicApiKey, this.groqApiKey, this.llmProviders, this.collections, this.tools, this.userId).catch((error) => {
|
|
7483
7331
|
logger.error("Failed to handle user prompt request:", error);
|
|
7484
7332
|
});
|
|
7485
7333
|
break;
|