2026-03-21 16:59:22 +00:00
|
|
|
|
{
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"name": "Chat_SQL_DbTableColumns_TableAgent_v29_AliasFix_FullyDynamic",
|
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 16:59:51 +00:00
|
|
|
|
-32912,
|
|
|
|
|
|
-3184
|
2026-03-21 16:59:22 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "18507856-9247-491e-8d4a-9159419d252e",
|
|
|
|
|
|
"name": "Webhook",
|
|
|
|
|
|
"webhookId": "b0b9c7f7-9eab-4227-8673-85e604bab2d2"
|
2026-03-21 16:59:22 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"jsCode": "\nconst body = $('Webhook').first().json.body || {};\nconst question = String(body.question || body.message || '').trim();\nreturn [{ json: {\n question,\n ollamaBody: {\n model: body.intentModel || 'qwen2.5:3b',\n stream: false,\n options: { temperature: 0, top_p: 0.1, num_predict: 12 },\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, listele/getir/say/toplam/raporla/sorgula/ara/filtrele gibi amaç 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 16:59:51 +00:00
|
|
|
|
-32704,
|
|
|
|
|
|
-3184
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "6067108d-b5dd-48df-9500-70416053db9f",
|
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 16:59:51 +00:00
|
|
|
|
-32480,
|
|
|
|
|
|
-3184
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "3baceaba-e945-47cd-b8c9-6561125f260d",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "Intent Ollama"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"jsCode": "\nconst 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();\nconst intent = content.includes('SQL') ? 'SQL' : 'CHAT';\nreturn [{ json: { question, tenantName, intent, intentRaw: res } }];\n"
|
2026-06-23 23:23:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
"type": "n8n-nodes-base.code",
|
|
|
|
|
|
"typeVersion": 2,
|
|
|
|
|
|
"position": [
|
2026-06-24 16:59:51 +00:00
|
|
|
|
-32256,
|
|
|
|
|
|
-3184
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "d4e9ab81-138e-4acc-a31d-e18be333d9ec",
|
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 16:59:51 +00:00
|
|
|
|
-32032,
|
|
|
|
|
|
-3184
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "0ae28c72-73f3-4851-b149-d4a2913418a9",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "Is SQL?"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-24 16:59:51 +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 || 'qwen2.5:3b',\n stream: false,\n options: { temperature: 0.2, top_p: 0.8, num_predict: 512 },\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 16:59:51 +00:00
|
|
|
|
-31824,
|
|
|
|
|
|
-2944
|
2026-03-21 16:59:22 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "6528b16c-51e1-4d19-9806-6d2f790ae6c2",
|
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 16:59:51 +00:00
|
|
|
|
-31600,
|
|
|
|
|
|
-2944
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "c5334fb9-eded-4903-bc2f-6454fd800e2a",
|
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 16:59:51 +00:00
|
|
|
|
-31376,
|
|
|
|
|
|
-2944
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "468e3ad7-1c0e-45db-bfd0-63b391247393",
|
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 16:59:51 +00:00
|
|
|
|
-31824,
|
|
|
|
|
|
-3360
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "d96b157a-de5e-4cc3-ba7d-ef2030ca7755",
|
|
|
|
|
|
"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 16:59:51 +00:00
|
|
|
|
-31584,
|
|
|
|
|
|
-3360
|
2026-03-21 16:59:22 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "70d35887-291c-41e5-acac-e5c5c60e0196",
|
|
|
|
|
|
"name": "Get Dictionary JSON",
|
|
|
|
|
|
"credentials": {
|
|
|
|
|
|
"microsoftSql": {
|
|
|
|
|
|
"id": "FV4335uaFWUGPlIf",
|
|
|
|
|
|
"name": "Microsoft SQL account"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-21 16:59:22 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-24 16:59:51 +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 16:59:51 +00:00
|
|
|
|
-31344,
|
|
|
|
|
|
-3360
|
2026-03-21 16:59:22 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "7b44191c-172d-464b-ba11-bdd5728d25fc",
|
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 16:59:51 +00:00
|
|
|
|
-31104,
|
|
|
|
|
|
-3360
|
2026-03-21 16:59:22 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "4ca40c6f-3605-4e7c-8beb-2d25784094d8",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "SQL Ollama"
|
2026-03-21 16:59:22 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-24 16:59:51 +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 16:59:51 +00:00
|
|
|
|
-30864,
|
|
|
|
|
|
-3360
|
2026-03-21 16:59:22 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "8d752e56-0e49-4bc9-8af4-4dade50bc690",
|
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 16:59:51 +00:00
|
|
|
|
-30640,
|
|
|
|
|
|
-3360
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "8a653bdf-3fc8-4cba-af8d-d3c827dfd739",
|
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 16:59:51 +00:00
|
|
|
|
-30416,
|
|
|
|
|
|
-3456
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "13a61ae4-27e2-4b0b-ab81-636281d81812",
|
|
|
|
|
|
"name": "Execute Generated SQL",
|
|
|
|
|
|
"credentials": {
|
|
|
|
|
|
"microsoftSql": {
|
|
|
|
|
|
"id": "FV4335uaFWUGPlIf",
|
|
|
|
|
|
"name": "Microsoft SQL account"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-23 23:23:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"parameters": {
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"jsCode": "\nconst rows = $input.all().map(item => item.json);\nconst v = $('Validate SQL').first().json || {};\nreturn [{ json: {\n type: 'sql_result',\n question: v.question,\n sql: v.sql,\n rowCount: rows.length,\n columns: rows.length > 0 ? Object.keys(rows[0]) : [],\n rows,\n debug: { usedTables: v.usedTables, candidateFormCodes: v.candidateFormCodes, dictionaryMeta: v.dictionaryMeta }\n}}];\n"
|
2026-06-23 23:23:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
"type": "n8n-nodes-base.code",
|
|
|
|
|
|
"typeVersion": 2,
|
|
|
|
|
|
"position": [
|
2026-06-24 16:59:51 +00:00
|
|
|
|
-30176,
|
|
|
|
|
|
-3456
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "c6325122-ac66-4ecc-845b-61d8b8b9a0c2",
|
2026-06-23 23:23:23 +00:00
|
|
|
|
"name": "Format SQL Result"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"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 16:59:51 +00:00
|
|
|
|
-30416,
|
|
|
|
|
|
-3216
|
2026-06-23 23:23:23 +00:00
|
|
|
|
],
|
2026-06-24 16:59:51 +00:00
|
|
|
|
"id": "6969c1e6-1c67-4305-a4c3-937e66ce3dea",
|
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": [
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
|
|
|
|
|
"node": "Format SQL Result",
|
|
|
|
|
|
"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 16:59:51 +00:00
|
|
|
|
"versionId": "22ff5d0b-ab86-47fb-8251-c38258384419",
|
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": []
|
|
|
|
|
|
}
|