2026-03-21 16:59:22 +00:00
|
|
|
|
{
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"name": "Chat",
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"nodes": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
|
|
|
|
|
"multipleMethods": true,
|
|
|
|
|
|
"httpMethod": [
|
|
|
|
|
|
"POST"
|
|
|
|
|
|
],
|
|
|
|
|
|
"path": "chat",
|
|
|
|
|
|
"responseMode": "lastNode",
|
|
|
|
|
|
"responseData": "allEntries",
|
|
|
|
|
|
"options": {}
|
|
|
|
|
|
},
|
|
|
|
|
|
"type": "n8n-nodes-base.webhook",
|
|
|
|
|
|
"typeVersion": 2,
|
|
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-39376,
|
|
|
|
|
|
-3072
|
2026-03-21 16:59:22 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "46b4fe58-58c7-4344-a6fb-c099caab6651",
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"name": "Webhook",
|
|
|
|
|
|
"webhookId": "b0b9c7f7-9eab-4227-8673-85e604bab2d2"
|
2026-03-21 16:59:22 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"jsCode": "const body = $('Webhook').first().json.body || {};\nconst question = String(body.question || body.message || '').trim();\nreturn [{ json: {\n question,\n ollamaBody: {\n model: body.intentModel || 'gemma3:4b',\n stream: false,\n options: { temperature: 0, top_p: 0.1, num_ctx: 1024, num_predict: 8 },\n messages: [\n { role: 'system', content: 'Sen bir niyet sınıflandırma motorusun. Sadece CHAT veya SQL döndür. Kullanıcı veritabanından veri istiyorsa SQL döndür. Listele, getir, göster, say, kaç, adet, toplam, özet, rapor, sorgula, ara, filtrele, grupla, gruplandır, bazında, kırılım, pivot, tanımlama, kayıt, tablo, kolon, müşteri, sipariş, stok, ayar gibi veri/tablo amaçları varsa SQL döndür. Selamlaşma, açıklama, yardım, teşekkür, genel sohbet ise CHAT döndür. Açıklama yazma.' },\n { role: 'user', content: question }\n ]\n }\n}}];\n"
|
2026-03-21 16:59:22 +00:00
|
|
|
|
},
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"type": "n8n-nodes-base.code",
|
|
|
|
|
|
"typeVersion": 2,
|
|
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-39168,
|
|
|
|
|
|
-3072
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "f5ab8edd-fd83-4e5c-94cc-6c58ff80bad6",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "Build Intent Request"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
|
|
|
|
|
"method": "POST",
|
|
|
|
|
|
"url": "http://ollama:11434/api/chat",
|
|
|
|
|
|
"sendBody": true,
|
|
|
|
|
|
"specifyBody": "json",
|
|
|
|
|
|
"jsonBody": "={{ $json.ollamaBody }}",
|
|
|
|
|
|
"options": {}
|
|
|
|
|
|
},
|
|
|
|
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
|
|
|
|
"typeVersion": 4.2,
|
|
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-38944,
|
|
|
|
|
|
-3072
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "d9a8bd55-350e-40dd-b145-2f70aaf76f79",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "Intent Ollama"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"jsCode": "const res = $input.first().json || {};\nconst body = $('Webhook').first().json.body || {};\nconst content = String(res.message?.content || res.response || res.output || '').toUpperCase();\nconst question = String(body.question || body.message || '').trim();\nconst tenantName = String(body.tenantName || 'Sozsoft').trim();\n\nfunction normalizeText(value) {\n return String(value || '')\n .toLocaleLowerCase('tr-TR')\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/ı/g, 'i')\n .replace(/ğ/g, 'g')\n .replace(/ü/g, 'u')\n .replace(/ş/g, 's')\n .replace(/ö/g, 'o')\n .replace(/ç/g, 'c')\n .replace(/[^a-z0-9]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nconst q = normalizeText(question);\nconst tokens = new Set(q.split(' ').filter(Boolean));\n\n// LLM yanlışlıkla CHAT dese bile bu kelimeler veri sorgusu niyetidir.\nconst sqlActionWords = [\n 'listele','liste','listesi','getir','goster','ara','bul','sorgula','filtrele','raporla','rapor',\n 'say','sayisi','kac','adet','toplam','ozet','ozetle','sum','count',\n 'grupla','gruplandir','gruplama','bazinda','kirilim','pivot','group','grouping',\n 'select','sql'\n];\n\nconst dataObjectWords = [\n 'tanim','tanimlama','tanimlamalari','ayar','ayarlari','kayit','kayitlari','tablo','kolon',\n 'musteri','siparis','stok','urun','firma','depo','fatura','personel','kumas','iplik'\n];\n\nconst hasSqlAction = sqlActionWords.some(w => tokens.has(w) || q.includes(w));\nconst hasDataObject = dataObjectWords.some(w => tokens.has(w) || q.includes(w));\nconst forceSql = body.forceSql === true || body.intent === 'SQL' || body.type === 'sql';\n\nlet intent;\nif (forceSql || hasSqlAction || (hasDataObject && /\\b(ana|grup|anahtar|key|bazinda|toplam|ozet|adet|sayisi)\\b/.test(q))) {\n intent = 'SQL';\n} else {\n intent = content.includes('SQL') ? 'SQL' : 'CHAT';\n}\n\nreturn [{ json: { question, tenantName, intent, intentRaw: res, intentByRule: intent === 'SQL' && !content.includes('SQL') } }];\n"
|
2026-06-23 23:23:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
"type": "n8n-nodes-base.code",
|
|
|
|
|
|
"typeVersion": 2,
|
|
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-38720,
|
|
|
|
|
|
-3072
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "27993516-1b97-422e-b026-162afb4fb99b",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "Normalize Intent"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
|
|
|
|
|
"conditions": {
|
|
|
|
|
|
"options": {
|
|
|
|
|
|
"caseSensitive": true,
|
|
|
|
|
|
"leftValue": "",
|
|
|
|
|
|
"typeValidation": "strict",
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"version": 1
|
2026-06-23 23:23:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
"conditions": [
|
|
|
|
|
|
{
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "89f40b61-7147-4055-8ffb-5e24ad9f7879",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"leftValue": "={{ $json.intent }}",
|
|
|
|
|
|
"rightValue": "SQL",
|
|
|
|
|
|
"operator": {
|
|
|
|
|
|
"type": "string",
|
|
|
|
|
|
"operation": "equals"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"combinator": "and"
|
|
|
|
|
|
},
|
|
|
|
|
|
"options": {}
|
|
|
|
|
|
},
|
|
|
|
|
|
"type": "n8n-nodes-base.if",
|
|
|
|
|
|
"typeVersion": 2,
|
|
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-38496,
|
|
|
|
|
|
-3072
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "2b7ee598-6977-4f0a-921c-11411576b1a9",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "Is SQL?"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"jsCode": "\nconst body = $('Webhook').first().json.body || {};\nconst question = String(body.question || body.message || '').trim();\nreturn [{ json: { type:'chat', question, ollamaBody: {\n model: body.chatModel || 'gemma3:4b',\n stream: false,\n options: { temperature: 0.2, top_p: 0.8, num_ctx: 2048, num_predict: 384 },\n messages: [\n { role: 'system', content: 'Sen Türkçe konuşan yardımcı bir asistansın. Kullanıcı SQL/veri istemiyorsa kısa ve net cevap ver. Veritabanı sorgusu üretme.' },\n { role: 'user', content: question }\n ]\n}}}];\n"
|
2026-06-23 23:23:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
"type": "n8n-nodes-base.code",
|
|
|
|
|
|
"typeVersion": 2,
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-38288,
|
|
|
|
|
|
-2832
|
2026-03-21 16:59:22 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "f5fa30e8-2934-4bf7-a2ae-96dc98acb64a",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "Build Chat Request"
|
2026-03-21 16:59:22 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"method": "POST",
|
|
|
|
|
|
"url": "http://ollama:11434/api/chat",
|
|
|
|
|
|
"sendBody": true,
|
|
|
|
|
|
"specifyBody": "json",
|
|
|
|
|
|
"jsonBody": "={{ $json.ollamaBody }}",
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"options": {}
|
|
|
|
|
|
},
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
|
|
|
|
"typeVersion": 4.2,
|
|
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-38064,
|
|
|
|
|
|
-2832
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "e68d00f6-fe8d-4f6a-8170-fce9cdd14f0a",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "Chat Ollama"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"jsCode": "const res=$input.first().json||{}; return [{json:{type:'chat', question:$('Webhook').first().json.body?.question || '', answer:res.message?.content || res.response || res.output || ''}}];"
|
2026-06-23 23:23:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
"type": "n8n-nodes-base.code",
|
|
|
|
|
|
"typeVersion": 2,
|
|
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-37840,
|
|
|
|
|
|
-2832
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "7cde5a7e-e0f2-497c-a38a-62de4084241d",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "Chat Response"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"jsCode": "const body = $('Webhook').first().json.body || {};\n\nconst question = String(body.question || body.message || '').trim();\nconst tenantName = String(body.tenantName || 'Sozsoft').trim();\nconst definitionCultureName = body.definitionCultureName || 'en';\nconst uiCultureName = body.uiCultureName || 'tr';\nconst listFormCode = body.listFormCode || null;\nconst onlyBaseDefinitions = body.onlyBaseDefinitions === false ? 0 : 1;\nconst resourceName = body.resourceName || null;\nconst maxForms = Number(body.maxForms || 500);\n\nfunction q(v) {\n if (v === null || v === undefined || v === '') return 'NULL';\n return \"N'\" + String(v).replace(/'/g, \"''\") + \"'\";\n}\n\nconst safeMaxForms = Number.isFinite(maxForms) && maxForms > 0 ? Math.min(maxForms, 2000) : 500;\n\n// v23: Sadece dbo.Sas_H_DbTableColumn tablosundan okur.\nconst dictionaryQuery = `\nSELECT TOP (1)\n JsonData AS ListFormDataDictionaryJson,\n CacheKey,\n RefreshedAt,\n FormCount,\n FieldCount,\n JsonLength\nFROM dbo.Sas_H_DbTableColumn WITH (NOLOCK)\nWHERE DictionaryType = N'ListFormJson'\n AND ISNULL(IsActive, 0) = 1\n AND DefinitionCultureName = ${q(definitionCultureName)}\n AND UiCultureName = ${q(uiCultureName)}\n AND ISNULL(ListFormCode, N'') = ISNULL(${q(listFormCode)}, N'')\n AND ISNULL(OnlyBaseDefinitions, 1) = ${onlyBaseDefinitions}\n AND ISNULL(ResourceName, N'') = ISNULL(${q(resourceName)}, N'')\n AND ISNULL(MaxForms, 500) = ${safeMaxForms}\nORDER BY RefreshedAt DESC;\n`;\n\nreturn [{\n json: {\n question,\n tenantName,\n definitionCultureName,\n uiCultureName,\n listFormCode,\n onlyBaseDefinitions,\n resourceName,\n maxForms: safeMaxForms,\n dictionaryQuery\n }\n}];"
|
2026-06-23 23:23:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
"type": "n8n-nodes-base.code",
|
|
|
|
|
|
"typeVersion": 2,
|
|
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-38288,
|
|
|
|
|
|
-3248
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "326e9983-14e0-490e-bcb8-4852bbca1d49",
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"name": "Build Dictionary Query"
|
2026-06-23 23:23:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
|
|
|
|
|
"operation": "executeQuery",
|
|
|
|
|
|
"query": "={{ $json.dictionaryQuery }}"
|
|
|
|
|
|
},
|
|
|
|
|
|
"type": "n8n-nodes-base.microsoftSql",
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"typeVersion": 1.1,
|
|
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-38048,
|
|
|
|
|
|
-3248
|
2026-03-21 16:59:22 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "986bc23a-44ac-48e6-bd68-96c70b48e7dc",
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"name": "Get Dictionary JSON",
|
|
|
|
|
|
"credentials": {
|
|
|
|
|
|
"microsoftSql": {
|
|
|
|
|
|
"id": "FV4335uaFWUGPlIf",
|
|
|
|
|
|
"name": "Microsoft SQL account"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-21 16:59:22 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"jsCode": "\nconst row = $input.first().json || {};\nconst body = $('Webhook').first().json.body || {};\n\nconst question = String(body.question || body.message || '').trim();\nconst tenantName = String(body.tenantName || 'Sozsoft').trim();\nconst dictText = String(row.ListFormDataDictionaryJson || '').trim();\n\nif (!dictText) {\n return [{\n json: {\n valid: false,\n type: 'dictionary_error',\n error: 'Sas_H_DbTableColumn tablosunda aktif JSON veri sözlüğü bulunamadı. Önce JSON üretip dbo.Sas_H_DbTableColumn tablosuna yazın.',\n question\n }\n }];\n}\n\nlet dict;\ntry {\n dict = JSON.parse(dictText);\n} catch (e) {\n return [{\n json: {\n valid: false,\n type: 'dictionary_error',\n error: 'JsonData parse edilemedi: ' + e.message,\n question\n }\n }];\n}\n\nconst listForms = Array.isArray(dict.listForms) ? dict.listForms : [];\n\nfunction escapeRegExp(s) {\n return String(s || '').replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction normalizeText(value) {\n return String(value || '')\n .replace(/\\\\u0022/g, '')\n .replace(/\\u0022/g, '')\n .replace(/"/gi, '')\n .replace(/[\\\"'`[\\]\\\\]/g, ' ')\n .toLocaleLowerCase('tr-TR')\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/ı/g, 'i')\n .replace(/ğ/g, 'g')\n .replace(/ü/g, 'u')\n .replace(/ş/g, 's')\n .replace(/ö/g, 'o')\n .replace(/ç/g, 'c')\n .replace(/[^a-z0-9]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction decodeSqlEscapes(value) {\n return String(value || '')\n .replace(/\\\\u0022/g, '')\n .replace(/\\u0022/g, '')\n .replace(/u0022/g, '')\n .replace(/\\\\u0027/g, \"'\")\n .replace(/\\u0027/g, \"'\")\n .replace(/u0027/g, \"'\")\n .replace(/"/gi, '')\n .replace(/'/gi, \"'\");\n}\n\nfunction cleanIdentifier(value) {\n let s = decodeSqlEscapes(value)\n .replace(/LookupQuery\\s*:/gi, '')\n .replace(/[\"'`\\[\\]\\\\]/g, '')\n .replace(/\\s+AS\\s+(KEY|NAME)$/i, '')\n .replace(/^dbo\\./i, '')\n .trim();\n\n const dbRe = new RegExp('^' + escapeRegExp(tenantName) + '\\\\.', 'i');\n s = s.replace(dbRe, '').replace(/^dbo\\./i, '').trim();\n\n const parts = s.split('.').map(x => x.trim()).filter(Boolean);\n s = parts.length ? parts[parts.length - 1] : s;\n\n // Only allow real identifier tokens. SQL expressions such as CONCAT(Name,' (',DisplayName,')') are not column names.\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(s)) return '';\n return s;\n}\n\nfunction simpleIdentifierFromExpression(value) {\n let s = decodeSqlEscapes(value).trim();\n s = s.replace(/^\\s*SELECT\\s+/i, '').replace(/\\s+AS\\s+(KEY|NAME)\\s*$/i, '').trim();\n\n // remove outer brackets/quotes/backticks for a simple identifier only\n if (/^\\[[A-Za-z_][A-Za-z0-9_]*\\]$/.test(s)) s = s.slice(1, -1);\n if (/^\"[A-Za-z_][A-Za-z0-9_]*\"$/.test(s)) s = s.slice(1, -1);\n if (/^`[A-Za-z_][A-Za-z0-9_]*`$/.test(s)) s = s.slice(1, -1);\n\n // dbo.Column or Table.Column => use last part only if all parts are simple identifiers\n const parts = s.split('.').map(x => x.replace(/[\\[\\]\"`]/g, '').trim()).filter(Boolean);\n if (parts.length && parts.every(x => /^[A-Za-z_][A-Za-z0-9_]*$/.test(x))) return parts[parts.length - 1];\n\n return '';\n}\n\nfunction bracketIdentifier(value, fallback = 'Id') {\n const id = cleanIdentifier(value) || cleanIdentifier(fallback) || 'Id';\n return '[' + id.replace(/]/g, ']]') + ']';\n}\n\nfunction cleanAliasLabel(value, fallback) {\n let label = decodeSqlEscapes(value)\n .replace(/\\r|\\n|\\t/g, ' ')\n .replace(/[\\[\\]]/g, '')\n .replace(/[\\u0000-\\u001F]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n\n // Alias kullanıcıya görünen başlıktır; Türkçe karakter ve boşluk kalabilir.\n // Ancak SQL ifadesi veya boş değer alias olamaz.\n if (!label || /^(CONCAT|CASE|SELECT|NULLIF|ISNULL|COALESCE)\\s*\\(/i.test(label)) {\n label = decodeSqlEscapes(fallback).replace(/[\\[\\]]/g, '').trim();\n }\n if (!label) label = 'Column';\n
|
2026-03-21 16:59:22 +00:00
|
|
|
|
},
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"type": "n8n-nodes-base.code",
|
|
|
|
|
|
"typeVersion": 2,
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-37808,
|
|
|
|
|
|
-3248
|
2026-03-21 16:59:22 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "61007e7c-6f51-4572-ad61-13d9f0bffa4c",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "Build SQL Request"
|
2026-03-21 16:59:22 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"method": "POST",
|
|
|
|
|
|
"url": "http://ollama:11434/api/chat",
|
|
|
|
|
|
"sendBody": true,
|
|
|
|
|
|
"specifyBody": "json",
|
|
|
|
|
|
"jsonBody": "={{ $json.ollamaBody }}",
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"options": {}
|
|
|
|
|
|
},
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
|
|
|
|
"typeVersion": 4.2,
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-37568,
|
|
|
|
|
|
-3248
|
2026-03-21 16:59:22 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "2b6630c2-f520-472a-8733-06e362264849",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "SQL Ollama"
|
2026-03-21 16:59:22 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"jsCode": "const res = $input.first().json || {};\nconst build = $('Build SQL Request').first().json || {};\nconst question = build.question || $('Webhook').first().json.body?.question || '';\nconst tenantName = String(build.tenantName || $('Webhook').first().json.body?.tenantName || 'Sozsoft').trim();\n\nfunction escapeRegExp(s) {\n return String(s || '').replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction cleanName(v) {\n let s = String(v || '')\n .replace(/\\\\u0022/g, '')\n .replace(/\\u0022/g, '')\n .replace(/"/gi, '')\n .replace(/LookupQuery\\s*:/gi, '')\n .replace(/[\"`\\[\\]\\\\]/g, '')\n .replace(/^dbo\\./i, '')\n .trim();\n\n const dbRe = new RegExp('^' + escapeRegExp(tenantName) + '\\\\.', 'i');\n s = s.replace(dbRe, '').replace(/^dbo\\./i, '').trim();\n\n const parts = s.split('.').map(x => x.trim()).filter(Boolean);\n return parts.length ? parts[parts.length - 1] : s;\n}\n\nconst allowedTables = new Set((build.allowedTables || []).map(cleanName).filter(Boolean));\n\nfunction getOllamaText(obj) {\n return String(\n obj.message?.content ||\n obj.response ||\n obj.output ||\n obj.text ||\n obj.content ||\n ''\n ).trim();\n}\n\nconst raw = getOllamaText(res);\n\nfunction cleanSql(text) {\n let t = String(text || '')\n .replace(/```sql/gi, '')\n .replace(/```/g, '')\n .replace(/^SQL\\s*:/i, '')\n .replace(/\\\\u0022/g, '')\n .replace(/\\u0022/g, '')\n .replace(/"/gi, '')\n .replace(/\"/g, '')\n .trim();\n\n const idx = t.search(/\\bSELECT\\b/i);\n if (idx < 0) return '';\n\n t = t.substring(idx).trim();\n const semi = t.indexOf(';');\n if (semi >= 0) t = t.substring(0, semi).trim();\n return t.replace(/\\s+/g, ' ').trim();\n}\n\nfunction canonicalizeSql(sql) {\n let q = String(sql || '')\n .replace(/\\\\u0022/g, '')\n .replace(/\\u0022/g, '')\n .replace(/"/gi, '')\n .replace(/\\[LookupQuery:\\s*([^\\]]+)\\]/gi, '[$1]');\n\n const db = escapeRegExp(tenantName);\n\n // [Sozsoft].[Sozsoft].[dbo].[X] -> [Sozsoft].[dbo].[X]\n q = q.replace(new RegExp('\\\\[' + db + '\\\\]\\\\.\\\\[' + db + '\\\\]\\\\.\\\\[dbo\\\\]\\\\.', 'gi'), '[' + tenantName + '].[dbo].');\n q = q.replace(new RegExp(db + '\\\\.' + db + '\\\\.dbo\\\\.', 'gi'), '[' + tenantName + '].[dbo].');\n\n // [dbo].[X] -> [Sozsoft].[dbo].[X], eğer önünde zaten [Sozsoft]. yoksa\n q = q.replace(/\\[dbo\\]\\.\\[([^\\]]+)\\]/gi, (m, t, off, str) => {\n const before = str.slice(Math.max(0, off - tenantName.length - 4), off);\n const alreadyDb = new RegExp('\\\\[' + db + '\\\\]\\\\.$', 'i').test(before);\n if (alreadyDb) return m;\n return '[' + tenantName + '].[dbo].[' + cleanName(t) + ']';\n });\n\n // DİKKAT: Köşeli parantez içindeki her şeyi temizleme.\n // Aliaslar [Adı], [Üst Adı], [Çalışma Saati] gibi Türkçe/boşluklu olabilir.\n // Global bracket temizliği aliasları [] haline getirir.\n return q.replace(/\\s+/g, ' ').trim();\n}\n\nfunction qualifyTables(sql) {\n let q = canonicalizeSql(sql);\n const tables = Array.from(allowedTables).sort((a, b) => b.length - a.length);\n\n for (const table of tables) {\n const t = escapeRegExp(table);\n\n // FROM dbo.Table / JOIN dbo.Table\n q = q.replace(new RegExp('\\\\b(FROM|JOIN)\\\\s+dbo\\\\.\\\\[?' + t + '\\\\]?', 'gi'), '$1 [' + tenantName + '].[dbo].[' + table + ']');\n\n // FROM [dbo].[Table] / JOIN [dbo].[Table]\n q = q.replace(new RegExp('\\\\b(FROM|JOIN)\\\\s+\\\\[dbo\\\\]\\\\.\\\\[' + t + '\\\\]', 'gi'), '$1 [' + tenantName + '].[dbo].[' + table + ']');\n\n // FROM Table / JOIN Table veya FROM [Table] / JOIN [Table]\n q = q.replace(new RegExp('\\\\b(FROM|JOIN)\\\\s+\\\\[?' + t + '\\\\]?(?=\\\\s|$)', 'gi'), '$1 [' + tenantName + '].[dbo].[' + table + ']');\n }\n\n return canonicalizeSql(q);\n}\n\nfunction extractTables(sql) {\n const tables = [];\n const re = /\\b(?:FROM|JOIN)\\s+((?:\\[[^\\]]+\\]\\.){0,3}\\[[^\\]]+\\]|(?:[A-Za-z0-9_]+\\.){0,3}[A-Za-z0-9_]+)/gi;\n let m;\n while ((m = re.exec(sql)) !== null) {\n const
|
2026-03-21 16:59:22 +00:00
|
|
|
|
},
|
|
|
|
|
|
"type": "n8n-nodes-base.code",
|
|
|
|
|
|
"typeVersion": 2,
|
|
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-37328,
|
|
|
|
|
|
-3248
|
2026-03-21 16:59:22 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "2a9ba43b-f687-4c27-a00b-841d912cc9a1",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "Validate SQL"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
|
|
|
|
|
"conditions": {
|
|
|
|
|
|
"options": {
|
|
|
|
|
|
"caseSensitive": true,
|
|
|
|
|
|
"leftValue": "",
|
|
|
|
|
|
"typeValidation": "strict",
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"version": 1
|
2026-06-23 23:23:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
"conditions": [
|
|
|
|
|
|
{
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "c6195621-ef9a-4298-9317-ae22e6c86b92",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"leftValue": "={{ $json.valid }}",
|
|
|
|
|
|
"rightValue": true,
|
|
|
|
|
|
"operator": {
|
|
|
|
|
|
"type": "boolean",
|
|
|
|
|
|
"operation": "true",
|
|
|
|
|
|
"singleValue": true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"combinator": "and"
|
|
|
|
|
|
},
|
|
|
|
|
|
"options": {}
|
|
|
|
|
|
},
|
|
|
|
|
|
"type": "n8n-nodes-base.if",
|
|
|
|
|
|
"typeVersion": 2,
|
|
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-37104,
|
|
|
|
|
|
-3248
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "4445ba0d-c494-4389-be21-1b2468fade63",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "SQL Valid?"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
|
|
|
|
|
"operation": "executeQuery",
|
|
|
|
|
|
"query": "={{ $json.sql }}"
|
|
|
|
|
|
},
|
|
|
|
|
|
"type": "n8n-nodes-base.microsoftSql",
|
|
|
|
|
|
"typeVersion": 1.1,
|
|
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-36880,
|
|
|
|
|
|
-3344
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "ec109600-5013-4b38-aeee-be8f2b654864",
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"name": "Execute Generated SQL",
|
|
|
|
|
|
"credentials": {
|
|
|
|
|
|
"microsoftSql": {
|
|
|
|
|
|
"id": "FV4335uaFWUGPlIf",
|
|
|
|
|
|
"name": "Microsoft SQL account"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-23 23:23:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"jsCode": "\nconst rows = $input.all().map(item => item.json);\nconst v = $('Validate SQL').first().json || {};\nconst question = String(v.question || '').toLocaleLowerCase('tr-TR');\n\nfunction normalizeText(value) {\n return String(value || '')\n .toLocaleLowerCase('tr-TR')\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/ı/g, 'i')\n .replace(/ğ/g, 'g')\n .replace(/ü/g, 'u')\n .replace(/ş/g, 's')\n .replace(/ö/g, 'o')\n .replace(/ç/g, 'c')\n .replace(/[^a-z0-9]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction isNumberValue(value) {\n if (value === null || value === undefined || value === '') return false;\n if (typeof value === 'number') return Number.isFinite(value);\n return !isNaN(Number(String(value).replace(',', '.')));\n}\n\nfunction toNumber(value) {\n if (typeof value === 'number') return value;\n return Number(String(value).replace(',', '.'));\n}\n\nconst q = normalizeText(question);\nconst columns = rows.length > 0 ? Object.keys(rows[0]) : [];\n\nconst wantsPivot =\n q.includes('pivot') ||\n q.includes('bazinda') ||\n q.includes('kirilim') ||\n q.includes('gruplandir') ||\n q.includes('gruplama') ||\n q.includes('grouping') ||\n q.includes('group');\n\nconst wantsSummary =\n wantsPivot ||\n q.includes('ozet') ||\n q.includes('summarize') ||\n q.includes('summary') ||\n q.includes('toplam') ||\n q.includes('ortalama') ||\n q.includes('adet') ||\n q.includes('kac') ||\n q.includes('sayisi');\n\nif (!rows.length) {\n return [{\n json: {\n type: 'sql_result',\n mode: wantsPivot ? 'pivot' : wantsSummary ? 'summary' : 'list',\n question: v.question,\n sql: v.sql,\n rowCount: 0,\n columns,\n rows: [],\n debug: { sqlSource: v.sqlSource, usedTables: v.usedTables, candidateFormCodes: v.candidateFormCodes, dictionaryMeta: v.dictionaryMeta }\n }\n }];\n}\n\nconst numericColumns = columns.filter(col => rows.some(r => isNumberValue(r[col])));\nconst dimensionColumns = columns.filter(col => !numericColumns.includes(col));\n\nif (wantsSummary && !wantsPivot) {\n const summary = {\n totalRows: rows.length\n };\n\n for (const col of numericColumns) {\n const values = rows.map(r => toNumber(r[col])).filter(n => Number.isFinite(n));\n summary[col] = {\n toplam: values.reduce((a, b) => a + b, 0),\n ortalama: values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0,\n min: values.length ? Math.min(...values) : null,\n max: values.length ? Math.max(...values) : null\n };\n }\n\n return [{\n json: {\n type: 'sql_result',\n mode: 'summary',\n question: v.question,\n sql: v.sql,\n rowCount: rows.length,\n columns,\n summary,\n rows,\n debug: { sqlSource: v.sqlSource, usedTables: v.usedTables, candidateFormCodes: v.candidateFormCodes, dictionaryMeta: v.dictionaryMeta }\n }\n }];\n}\n\nif (wantsPivot) {\n // SQL zaten GROUP BY ile döndüyse doğrudan pivotlanabilir tablo gibi aktar.\n const hasAggregateColumn = columns.some(c => ['adet', 'count', 'total', 'toplam'].includes(normalizeText(c)));\n if (hasAggregateColumn) {\n return [{\n json: {\n type: 'sql_result',\n mode: 'pivot',\n question: v.question,\n sql: v.sql,\n rowCount: rows.length,\n columns,\n rows,\n debug: { sqlSource: v.sqlSource, usedTables: v.usedTables, candidateFormCodes: v.candidateFormCodes, dictionaryMeta: v.dictionaryMeta }\n }\n }];\n }\n\n const groupColumn = dimensionColumns[0] || columns[0];\n const valueColumn = numericColumns[0] || null;\n const map = {};\n\n for (const row of rows) {\n const key = row[groupColumn] ?? 'Boş';\n if (!map[key]) {\n map[key] = { [groupColumn]: key, Adet: 0 };\n if (valueColumn) map[key][`${valueColumn}_Toplam`] = 0;\n }\n\n map[key].Adet++;\n\n if (valueColumn && isNumberValue(row[valueColumn])) {\n map[key][`${valueColumn}_Toplam`] += toNumber(row[valueColumn]);\n }
|
2026-06-23 23:23:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
"type": "n8n-nodes-base.code",
|
|
|
|
|
|
"typeVersion": 2,
|
|
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-36640,
|
|
|
|
|
|
-3344
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "4797a41c-acb6-4a8b-b66c-e3cebc230dae",
|
|
|
|
|
|
"name": "Format Smart SQL Result"
|
2026-06-23 23:23:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"jsCode": "\nconst e = $input.first().json || {};\nreturn [{ json: { type: e.type || 'sql_error', question: e.question || $('Webhook').first().json.body?.question || '', error: e.error || 'SQL üretilemedi.', generatedText: e.generatedText || null, cleanedText: e.cleanedText || null, unknownTables: e.unknownTables || null, allowedTables: e.allowedTables || null } }];\n"
|
2026-06-23 23:23:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
"type": "n8n-nodes-base.code",
|
|
|
|
|
|
"typeVersion": 2,
|
|
|
|
|
|
"position": [
|
2026-06-24 20:46:32 +00:00
|
|
|
|
-36880,
|
|
|
|
|
|
-3104
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"id": "ce66daea-a390-43ae-bd7d-0a00d80c86be",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "SQL Error Response"
|
2026-03-21 16:59:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"pinData": {},
|
|
|
|
|
|
"connections": {
|
|
|
|
|
|
"Webhook": {
|
|
|
|
|
|
"main": [
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"node": "Build Intent Request",
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"type": "main",
|
|
|
|
|
|
"index": 0
|
|
|
|
|
|
}
|
2026-06-23 23:23:23 +00:00
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"Build Intent Request": {
|
|
|
|
|
|
"main": [
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
|
|
|
|
|
"node": "Intent Ollama",
|
|
|
|
|
|
"type": "main",
|
|
|
|
|
|
"index": 0
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"Intent Ollama": {
|
|
|
|
|
|
"main": [
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
|
|
|
|
|
"node": "Normalize Intent",
|
|
|
|
|
|
"type": "main",
|
|
|
|
|
|
"index": 0
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"Normalize Intent": {
|
|
|
|
|
|
"main": [
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
|
|
|
|
|
"node": "Is SQL?",
|
|
|
|
|
|
"type": "main",
|
|
|
|
|
|
"index": 0
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
2026-03-21 16:59:22 +00:00
|
|
|
|
]
|
|
|
|
|
|
},
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"Is SQL?": {
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"main": [
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"node": "Build Dictionary Query",
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"type": "main",
|
|
|
|
|
|
"index": 0
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"node": "Build Chat Request",
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"type": "main",
|
|
|
|
|
|
"index": 0
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"Build Chat Request": {
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"main": [
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"node": "Chat Ollama",
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"type": "main",
|
|
|
|
|
|
"index": 0
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"Chat Ollama": {
|
|
|
|
|
|
"main": [
|
2026-03-21 16:59:22 +00:00
|
|
|
|
[
|
|
|
|
|
|
{
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"node": "Chat Response",
|
|
|
|
|
|
"type": "main",
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"index": 0
|
2026-06-23 23:23:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"Build Dictionary Query": {
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"main": [
|
|
|
|
|
|
[
|
2026-03-21 16:59:22 +00:00
|
|
|
|
{
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"node": "Get Dictionary JSON",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"type": "main",
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"index": 0
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"Get Dictionary JSON": {
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"main": [
|
2026-03-21 16:59:22 +00:00
|
|
|
|
[
|
|
|
|
|
|
{
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"node": "Build SQL Request",
|
|
|
|
|
|
"type": "main",
|
|
|
|
|
|
"index": 0
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"Build SQL Request": {
|
|
|
|
|
|
"main": [
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
|
|
|
|
|
"node": "SQL Ollama",
|
|
|
|
|
|
"type": "main",
|
|
|
|
|
|
"index": 0
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"SQL Ollama": {
|
|
|
|
|
|
"main": [
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
|
|
|
|
|
"node": "Validate SQL",
|
|
|
|
|
|
"type": "main",
|
|
|
|
|
|
"index": 0
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"Validate SQL": {
|
|
|
|
|
|
"main": [
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
|
|
|
|
|
"node": "SQL Valid?",
|
|
|
|
|
|
"type": "main",
|
|
|
|
|
|
"index": 0
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"SQL Valid?": {
|
|
|
|
|
|
"main": [
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
|
|
|
|
|
"node": "Execute Generated SQL",
|
|
|
|
|
|
"type": "main",
|
|
|
|
|
|
"index": 0
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
|
|
|
|
|
"node": "SQL Error Response",
|
|
|
|
|
|
"type": "main",
|
|
|
|
|
|
"index": 0
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"Execute Generated SQL": {
|
|
|
|
|
|
"main": [
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"node": "Format Smart SQL Result",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"type": "main",
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"index": 0
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"active": true,
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"settings": {
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"executionOrder": "v1",
|
|
|
|
|
|
"availableInMCP": false,
|
|
|
|
|
|
"timeSavedMode": "fixed",
|
|
|
|
|
|
"callerPolicy": "workflowsFromSameOwner"
|
2026-03-21 16:59:22 +00:00
|
|
|
|
},
|
2026-06-24 20:46:32 +00:00
|
|
|
|
"versionId": "39108360-6275-4878-8546-69b1baa0c871",
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"meta": {
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"templateCredsSetupCompleted": true,
|
|
|
|
|
|
"instanceId": "1d288821beaaeeada5e8dce6f282c802098a0c83ef6ddb35a174b09a9d43850e"
|
2026-03-21 16:59:22 +00:00
|
|
|
|
},
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "MgXO29wLFA4urmNk",
|
2026-03-21 16:59:22 +00:00
|
|
|
|
"tags": []
|
|
|
|
|
|
}
|