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" : " \ n c o n s t r o w = $ i n p u t . f i r s t ( ) . j s o n | | { } ; \ n c o n s t b o d y = $ ( ' W e b h o o k ' ) . f i r s t ( ) . j s o n . b o d y | | { } ; \ n \ n c o n s t q u e s t i o n = S t r i n g ( b o d y . q u e s t i o n | | b o d y . m e s s a g e | | ' ' ) . t r i m ( ) ; \ n c o n s t t e n a n t N a m e = S t r i n g ( b o d y . t e n a n t N a m e | | ' S o z s o f t ' ) . t r i m ( ) ; \ n c o n s t d i c t T e x t = S t r i n g ( r o w . L i s t F o r m D a t a D i c t i o n a r y J s o n | | ' ' ) . t r i m ( ) ; \ n \ n i f ( ! d i c t T e x t ) { \ n r e t u r n [ { \ n j s o n : { \ n v a l i d : f a l s e , \ n t y p e : ' d i c t i o n a r y _ e r r o r ' , \ n e r r o r : ' S a s _ H _ D b T a b l e C o l u m n t a b l o s u n d a a k t i f J S O N v e r i s ö z l ü ğ ü b u l u n a m a d ı . Ö n c e J S O N ü r e t i p d b o . S a s _ H _ D b T a b l e C o l u m n t a b l o s u n a y a z ı n . ' , \ n q u e s t i o n \ n } \ n } ] ; \ n } \ n \ n l e t d i c t ; \ n t r y { \ n d i c t = J S O N . p a r s e ( d i c t T e x t ) ; \ n } c a t c h ( e ) { \ n r e t u r n [ { \ n j s o n : { \ n v a l i d : f a l s e , \ n t y p e : ' d i c t i o n a r y _ e r r o r ' , \ n e r r o r : ' J s o n D a t a p a r s e e d i l e m e d i : ' + e . m e s s a g e , \ n q u e s t i o n \ n } \ n } ] ; \ n } \ n \ n c o n s t l i s t F o r m s = A r r a y . i s A r r a y ( d i c t . l i s t F o r m s ) ? d i c t . l i s t F o r m s : [ ] ; \ n \ n f u n c t i o n e s c a p e R e g E x p ( s ) { \ n r e t u r n S t r i n g ( s | | ' ' ) . r e p l a c e ( / [ . * + ? ^ $ { } ( ) | [ \ \ ] \ \ \ \ ] / g , ' \ \ \ \ $ & ' ) ; \ n } \ n \ n f u n c t i o n n o r m a l i z e T e x t ( v a l u e ) { \ n r e t u r n S t r i n g ( v a l u e | | ' ' ) \ n . r e p l a c e ( / \ \ \ \ u 0 0 2 2 / g , ' ' ) \ n . r e p l a c e ( / \ \ u 0 0 2 2 / g , ' ' ) \ n . r e p l a c e ( / & q u o t ; / g i , ' ' ) \ n . r e p l a c e ( / [ \ \ \ "'`[\\]\\\\]/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 , ' ' ) . t r i m ( ) ) . f i l t e r ( B o o l e a n ) ; \ n i f ( p a r t s . l e n g t h & & p a r t s . e v e r y ( x = > / ^ [ A - Z a - z _ ] [ A - Z a - z 0 - 9 _ ] * $ / . t e s t ( x ) ) ) r e t u r n p a r t s [ p a r t s . l e n g t h - 1 ] ; \ n \ n r e t u r n ' ' ; \ n } \ n \ n f u n c t i o n b r a c k e t I d e n t i f i e r ( v a l u e , f a l l b a c k = ' I d ' ) { \ n c o n s t i d = c l e a n I d e n t i f i e r ( v a l u e ) | | c l e a n I d e n t i f i e r ( f a l l b a c k ) | | ' I d ' ; \ n r e t u r n ' [ ' + i d . r e p l a c e ( / ] / g , ' ] ] ' ) + ' ] ' ; \ n } \ n \ n f u n c t i o n c l e a n A l i a s L a b e l ( v a l u e , f a l l b a c k ) { \ n l e t l a b e l = d e c o d e S q l E s c a p e s ( v a l u e ) \ n . r e p l a c e ( / \ \ r | \ \ n | \ \ t / g , ' ' ) \ n . r e p l a c e ( / [ \ \ [ \ \ ] ] / g , ' ' ) \ n . r e p l a c e ( / [ \ \ u 0 0 0 0 - \ \ u 0 0 1 F ] / g , ' ' ) \ n . r e p l a c e ( / \ \ s + / g , ' ' ) \ n . t r i m ( ) ; \ 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" : " c o n s t r e s = $ i n p u t . f i r s t ( ) . j s o n | | { } ; \ n c o n s t b u i l d = $ ( ' B u i l d S Q L R e q u e s t ' ) . f i r s t ( ) . j s o n | | { } ; \ n c o n s t q u e s t i o n = b u i l d . q u e s t i o n | | $ ( ' W e b h o o k ' ) . f i r s t ( ) . j s o n . b o d y ? . q u e s t i o n | | ' ' ; \ n c o n s t t e n a n t N a m e = S t r i n g ( b u i l d . t e n a n t N a m e | | $ ( ' W e b h o o k ' ) . f i r s t ( ) . j s o n . b o d y ? . t e n a n t N a m e | | ' S o z s o f t ' ) . t r i m ( ) ; \ n \ n f u n c t i o n e s c a p e R e g E x p ( s ) { \ n r e t u r n S t r i n g ( s | | ' ' ) . r e p l a c e ( / [ . * + ? ^ $ { } ( ) | [ \ \ ] \ \ \ \ ] / g , ' \ \ \ \ $ & ' ) ; \ n } \ n \ n f u n c t i o n c l e a n N a m e ( v ) { \ n l e t s = S t r i n g ( v | | ' ' ) \ n . r e p l a c e ( / \ \ \ \ u 0 0 2 2 / g , ' ' ) \ n . r e p l a c e ( / \ \ u 0 0 2 2 / g , ' ' ) \ n . r e p l a c e ( / & q u o t ; / g i , ' ' ) \ n . r e p l a c e ( / L o o k u p Q u e r y \ \ s * : / g i , ' ' ) \ n . r e p l a c e ( / [ \ " ` \ \ [ \ \ ] \ \ \ \ ] / g , ' ' ) \ n . r e p l a c e ( / ^ d b o \ \ . / i , ' ' ) \ n . t r i m ( ) ; \ n \ n c o n s t d b R e = n e w R e g E x p ( ' ^ ' + e s c a p e R e g E x p ( t e n a n t N a m e ) + ' \ \ \ \ . ' , ' i ' ) ; \ n s = s . r e p l a c e ( d b R e , ' ' ) . r e p l a c e ( / ^ d b o \ \ . / i , ' ' ) . t r i m ( ) ; \ n \ n c o n s t p a r t s = s . s p l i t ( ' . ' ) . m a p ( x = > x . t r i m ( ) ) . f i l t e r ( B o o l e a n ) ; \ n r e t u r n p a r t s . l e n g t h ? p a r t s [ p a r t s . l e n g t h - 1 ] : s ; \ n } \ n \ n c o n s t a l l o w e d T a b l e s = n e w S e t ( ( b u i l d . a l l o w e d T a b l e s | | [ ] ) . m a p ( c l e a n N a m e ) . f i l t e r ( B o o l e a n ) ) ; \ n \ n f u n c t i o n g e t O l l a m a T e x t ( o b j ) { \ n r e t u r n S t r i n g ( \ n o b j . m e s s a g e ? . c o n t e n t | | \ n o b j . r e s p o n s e | | \ n o b j . o u t p u t | | \ n o b j . t e x t | | \ n o b j . c o n t e n t | | \ n ' ' \ n ) . t r i m ( ) ; \ n } \ n \ n c o n s t r a w = g e t O l l a m a T e x t ( r e s ) ; \ n \ n f u n c t i o n c l e a n S q l ( t e x t ) { \ n l e t t = S t r i n g ( t e x t | | ' ' ) \ n . r e p l a c e ( / ` ` ` s q l / g i , ' ' ) \ n . r e p l a c e ( / ` ` ` / g , ' ' ) \ n . r e p l a c e ( / ^ S Q L \ \ s * : / i , ' ' ) \ n . r e p l a c e ( / \ \ \ \ u 0 0 2 2 / g , ' ' ) \ n . r e p l a c e ( / \ \ u 0 0 2 2 / g , ' ' ) \ n . r e p l a c e ( / & q u o t ; / g i , ' ' ) \ n . r e p l a c e ( / \ " / g , ' ' ) \ n . t r i m ( ) ; \ n \ n c o n s t i d x = t . s e a r c h ( / \ \ b S E L E C T \ \ b / i ) ; \ n i f ( i d x < 0 ) r e t u r n ' ' ; \ n \ n t = t . s u b s t r i n g ( i d x ) . t r i m ( ) ; \ n c o n s t s e m i = t . i n d e x O f ( ' ; ' ) ; \ n i f ( s e m i > = 0 ) t = t . s u b s t r i n g ( 0 , s e m i ) . t r i m ( ) ; \ n r e t u r n t . r e p l a c e ( / \ \ s + / g , ' ' ) . t r i m ( ) ; \ n } \ n \ n f u n c t i o n c a n o n i c a l i z e S q l ( s q l ) { \ n l e t q = S t r i n g ( s q l | | ' ' ) \ n . r e p l a c e ( / \ \ \ \ u 0 0 2 2 / g , ' ' ) \ n . r e p l a c e ( / \ \ u 0 0 2 2 / g , ' ' ) \ n . r e p l a c e ( / & q u o t ; / g i , ' ' ) \ n . r e p l a c e ( / \ \ [ L o o k u p Q u e r y : \ \ s * ( [ ^ \ \ ] ] + ) \ \ ] / g i , ' [ $ 1 ] ' ) ; \ n \ n c o n s t d b = e s c a p e R e g E x p ( t e n a n t N a m e ) ; \ 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" : " \ n c o n s t r o w s = $ i n p u t . a l l ( ) . m a p ( i t e m = > i t e m . j s o n ) ; \ n c o n s t v = $ ( ' V a l i d a t e S Q L ' ) . f i r s t ( ) . j s o n | | { } ; \ n c o n s t q u e s t i o n = S t r i n g ( v . q u e s t i o n | | ' ' ) . t o L o c a l e L o w e r C a s e ( ' t r - T R ' ) ; \ n \ n f u n c t i o n n o r m a l i z e T e x t ( v a l u e ) { \ n r e t u r n S t r i n g ( v a l u e | | ' ' ) \ n . t o L o c a l e L o w e r C a s e ( ' t r - T R ' ) \ n . n o r m a l i z e ( ' N F D ' ) \ n . r e p l a c e ( / [ \ \ u 0 3 0 0 - \ \ u 0 3 6 f ] / g , ' ' ) \ n . r e p l a c e ( / ı / g , ' i ' ) \ n . r e p l a c e ( / ğ / g , ' g ' ) \ n . r e p l a c e ( / ü / g , ' u ' ) \ n . r e p l a c e ( / ş / g , ' s ' ) \ n . r e p l a c e ( / ö / g , ' o ' ) \ n . r e p l a c e ( / ç / g , ' c ' ) \ n . r e p l a c e ( / [ ^ a - z 0 - 9 ] + / g , ' ' ) \ n . r e p l a c e ( / \ \ s + / g , ' ' ) \ n . t r i m ( ) ; \ n } \ n \ n f u n c t i o n i s N u m b e r V a l u e ( v a l u e ) { \ n i f ( v a l u e = = = n u l l | | v a l u e = = = u n d e f i n e d | | v a l u e = = = ' ' ) r e t u r n f a l s e ; \ n i f ( t y p e o f v a l u e = = = ' n u m b e r ' ) r e t u r n N u m b e r . i s F i n i t e ( v a l u e ) ; \ n r e t u r n ! i s N a N ( N u m b e r ( S t r i n g ( v a l u e ) . r e p l a c e ( ' , ' , ' . ' ) ) ) ; \ n } \ n \ n f u n c t i o n t o N u m b e r ( v a l u e ) { \ n i f ( t y p e o f v a l u e = = = ' n u m b e r ' ) r e t u r n v a l u e ; \ n r e t u r n N u m b e r ( S t r i n g ( v a l u e ) . r e p l a c e ( ' , ' , ' . ' ) ) ; \ n } \ n \ n c o n s t q = n o r m a l i z e T e x t ( q u e s t i o n ) ; \ n c o n s t c o l u m n s = r o w s . l e n g t h > 0 ? O b j e c t . k e y s ( r o w s [ 0 ] ) : [ ] ; \ n \ n c o n s t w a n t s P i v o t = \ n q . i n c l u d e s ( ' p i v o t ' ) | | \ n q . i n c l u d e s ( ' b a z i n d a ' ) | | \ n q . i n c l u d e s ( ' k i r i l i m ' ) | | \ n q . i n c l u d e s ( ' g r u p l a n d i r ' ) | | \ n q . i n c l u d e s ( ' g r u p l a m a ' ) | | \ n q . i n c l u d e s ( ' g r o u p i n g ' ) | | \ n q . i n c l u d e s ( ' g r o u p ' ) ; \ n \ n c o n s t w a n t s S u m m a r y = \ n w a n t s P i v o t | | \ n q . i n c l u d e s ( ' o z e t ' ) | | \ n q . i n c l u d e s ( ' s u m m a r i z e ' ) | | \ n q . i n c l u d e s ( ' s u m m a r y ' ) | | \ n q . i n c l u d e s ( ' t o p l a m ' ) | | \ n q . i n c l u d e s ( ' o r t a l a m a ' ) | | \ n q . i n c l u d e s ( ' a d e t ' ) | | \ n q . i n c l u d e s ( ' k a c ' ) | | \ n q . i n c l u d e s ( ' s a y i s i ' ) ; \ n \ n i f ( ! r o w s . l e n g t h ) { \ n r e t u r n [ { \ n j s o n : { \ n t y p e : ' s q l _ r e s u l t ' , \ n m o d e : w a n t s P i v o t ? ' p i v o t ' : w a n t s S u m m a r y ? ' s u m m a r y ' : ' l i s t ' , \ n q u e s t i o n : v . q u e s t i o n , \ n s q l : v . s q l , \ n r o w C o u n t : 0 , \ n c o l u m n s , \ n r o w s : [ ] , \ n d e b u g : { s q l S o u r c e : v . s q l S o u r c e , u s e d T a b l e s : v . u s e d T a b l e s , c a n d i d a t e F o r m C o d e s : v . c a n d i d a t e F o r m C o d e s , d i c t i o n a r y M e t a : v . d i c t i o n a r y M e t a } \ n } \ n } ] ; \ n } \ n \ n c o n s t n u m e r i c C o l u m n s = c o l u m n s . f i l t e r ( c o l = > r o w s . s o m e ( r = > i s N u m b e r V a l u e ( r [ c o l ] ) ) ) ; \ n c o n s t d i m e n s i o n C o l u m n s = c o l u m n s . f i l t e r ( c o l = > ! n u m e r i c C o l u m n s . i n c l u d e s ( c o l ) ) ; \ n \ n i f ( w a n t s S u m m a r y & & ! w a n t s P i v o t ) { \ n c o n s t s u m m a r y = { \ n t o t a l R o w s : r o w s . l e n g t h \ n } ; \ n \ n f o r ( c o n s t c o l o f n u m e r i c C o l u m n s ) { \ n c o n s t v a l u e s = r o w s . m a p ( r = > t o N u m b e r ( r [ c o l ] ) ) . f i l t e r ( n = > N u m b e r . i s F i n i t e ( n ) ) ; \ n s u m m a r y [ c o l ] = { \ n t o p l a m : v a l u e s . r e d u c e ( ( a , b ) = > a + b , 0 ) , \ n o r t a l a m a : v a l u e s . l e n g t h ? v a l u e s . r e d u c e ( ( a , b ) = > a + b , 0 ) / v a l u e s . l e n g t h : 0 , \ n m i n : v a l u e s . l e n g t h ? M a t h . m i n ( . . . v a l u e s ) : n u l l , \ n m a x : v a l u e s . l e n g t h ? M a t h . m a x ( . . . v a l u e s ) : n u l l \ n } ; \ n } \ n \ n r e t u r n [ { \ n j s o n : { \ n t y p e : ' s q l _ r e s u l t ' , \ n m o d e : ' s u m m a r y ' , \ n q u e s t i o n : v . q u e s t i o n , \ n s q l : v . s q l , \ n r o w C o u n t : r o w s . l e n g t h , \ n c o l u m n s , \ n s u m m a r y , \ n r o w s , \ n d e b u g : { s q l S o u r c e : v . s q l S o u r c e , u s e d T a b l e s : v . u s e d T a b l e s , c a n d i d a t e F o r m C o d e s : v . c a n d i d a t e F o r m C o d e s , d i c t i o n a r y M e t a : v . d i c t i o n a r y M e t a } \ n } \ n } ] ; \ n } \ n \ n i f ( w a n t s P i v o t ) { \ 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" : [ ]
}