Guest User

Untitled

a guest
Sep 4th, 2025
5
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JSON 10.42 KB | Source Code | 0 0
  1. [
  2.     {
  3.         "id": "5dc8d9f249f7cf93",
  4.         "type": "group",
  5.         "z": "b2a201c573251bb8",
  6.         "name": "nr-assistant-self-hosted",
  7.         "style": {
  8.             "label": true
  9.         },
  10.         "nodes": [
  11.             "90b85bbb169ebd01",
  12.             "26a4798f99ba4c7f",
  13.             "59c6c5bf27ebe683"
  14.         ],
  15.         "env": [
  16.             {
  17.                 "name": "NODERED_URL",
  18.                 "value": "http://127.0.0.1:1880",
  19.                 "type": "str"
  20.             },
  21.             {
  22.                 "name": "NODERED_USER",
  23.                 "type": "cred"
  24.             },
  25.             {
  26.                 "name": "NODERED_PASS",
  27.                 "type": "cred"
  28.             },
  29.             {
  30.                 "name": "OPENAI_APIKEY",
  31.                 "type": "cred"
  32.             }
  33.         ],
  34.         "x": 34,
  35.         "y": 79,
  36.         "w": 592,
  37.         "h": 82
  38.     },
  39.     {
  40.         "id": "90b85bbb169ebd01",
  41.         "type": "http in",
  42.         "z": "b2a201c573251bb8",
  43.         "g": "5dc8d9f249f7cf93",
  44.         "name": "",
  45.         "url": "/admin/nr-assistant/:method",
  46.         "method": "post",
  47.         "upload": false,
  48.         "skipBodyParsing": false,
  49.         "swaggerDoc": "",
  50.         "x": 200,
  51.         "y": 120,
  52.         "wires": [
  53.             [
  54.                 "59c6c5bf27ebe683"
  55.             ]
  56.         ]
  57.     },
  58.     {
  59.         "id": "26a4798f99ba4c7f",
  60.         "type": "http response",
  61.         "z": "b2a201c573251bb8",
  62.         "g": "5dc8d9f249f7cf93",
  63.         "name": "",
  64.         "statusCode": "",
  65.         "headers": {},
  66.         "x": 550,
  67.         "y": 120,
  68.         "wires": []
  69.     },
  70.     {
  71.         "id": "59c6c5bf27ebe683",
  72.         "type": "function",
  73.         "z": "b2a201c573251bb8",
  74.         "g": "5dc8d9f249f7cf93",
  75.         "name": "assistant",
  76.         "func": "const request = msg.payload;\nconst flows = await getFlows();\n\nswitch (request.context.scope) {\n  case \"node\":\n\n    msg.payload =\n    {\n      \"data\": {\n        ...await llmNodeFlow(request.prompt)\n      }\n    }\n    break;\n\n  case \"inline\":\n    const flow = await flows.filter(f => f.id == request.transactionId.split(\"-\")[0]);\n    switch (request.context.type) {\n      case \"function\":\n        msg.payload =\n        {\n          \"data\": {\n            \"transactionId\": request.transactionId,\n            \"data\": {\n              ...await llmNodeInlineFunction(request.prompt, flow, request.context)\n            }\n          }\n        }\n        break;\n      case \"json\":\n        // not implemented\n        msg.payload =\n        {\n          \"data\": {\n            \"transactionId\": request.transactionId,\n            \"data\": {\n              \"json\": \"{\\n  \\\"name\\\": \\\"example\\\",\\n  \\\"enabled\\\": true\\n}\"\n            }\n          }\n        }\n        break;\n      case \"css\":\n        // not implemented\n        msg.payload =\n        {\n          \"data\": {\n            \"transactionId\": request.transactionId,\n            \"data\": {\n              \"css\": \".panel { display: grid; gap: 8px; }\"\n            }\n          }\n        }\n        break;\n      case \"html\":\n        // not implemented\n        msg.payload =\n        {\n          \"data\": {\n            \"transactionId\": request.transactionId,\n            \"data\": {\n              \"html\": \"<div id=\\\"app\\\">\\n  <h3>{{ title }}</h3>\\n</div>\\n<script>\\nexport default { data(){ return { title: 'Hello' } } }\\n</script>\"\n            }\n          }\n        }\n        break;\n      case \"sql\":\n        // not implemented\n        msg.payload =\n        {\n          \"data\": {\n            \"transactionId\": request.transactionId,\n            \"data\": {\n              \"sql\": \"SELECT id, name FROM public.users WHERE active = true ORDER BY name;\"\n            }\n          }\n        }\n        break;\n    }\n    break;\n\n}\n\nreturn msg;\n\n\n\nasync function llmNodeFlow(prompt) {\n  var messages = [\n    {\n      \"role\": \"system\",\n      \"content\": \"You are a Node-RED expert and are asked to assist with generating code.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": prompt\n    }\n  ]\n  var schema = {\n    \"type\": \"object\",\n    \"properties\": {\n      \"flow\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"id\": {\n              \"type\": \"string\",\n              \"pattern\": \"^[0-9a-fA-F]{16}$\",\n              \"minLength\": 16,\n              \"maxLength\": 16,\n              \"description\": \"Unique 16-character hex string\"\n            },\n            \"type\": {\n              \"type\": \"string\"\n            },\n            \"z\": {\n              \"type\": \"string\"\n            },\n            \"name\": {\n              \"type\": \"string\"\n            },\n            \"func\": {\n              \"type\": \"string\"\n            },\n            \"outputs\": {\n              \"type\": \"integer\"\n            },\n            \"noerr\": {\n              \"type\": \"integer\"\n            },\n            \"initialize\": {\n              \"type\": \"string\"\n            },\n            \"finalize\": {\n              \"type\": \"string\"\n            },\n            \"libs\": {\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\"\n              }\n            }\n          },\n          \"required\": [\n            \"id\",\n            \"type\",\n            \"z\",\n            \"name\",\n            \"func\",\n            \"outputs\",\n            \"noerr\",\n            \"initialize\",\n            \"finalize\",\n            \"libs\"\n          ],\n          \"additionalProperties\": false\n        }\n      }\n    },\n    \"required\": [\"flow\"],\n    \"additionalProperties\": false\n  }\n  const response = await fetch(\"https://api.openai.com/v1/chat/completions\", {\n    method: \"POST\",\n    headers: {\n      \"Authorization\": `Bearer ${env.get(\"CREDENTIALS_OPENAI\")}`,\n      \"Content-Type\": \"application/json\"\n    },\n    body: JSON.stringify({\n      max_completion_tokens: 3000,\n      frequency_penalty: 0,\n      presence_penalty: 0,\n      model: \"gpt-5-mini\",\n      reasoning_effort: \"minimal\",\n      messages,\n      response_format: {\n        type: \"json_schema\",\n        json_schema: {\n          name: \"NodeRedFlow\",\n          description: \"Author a Node-RED flow.\",\n          strict: true,\n          schema\n        }\n      }\n    })\n  });\n  if (!response.ok) {\n    const err = await response.json().catch(() => ({}));\n    throw new Error(`OpenAI API error: ${response.status} ${response.statusText}\\n${JSON.stringify(err, null, 2)}`);\n  }\n  const data = await response.json();\n  return JSON.parse(data.choices[0].message.content);\n}\n\nasync function llmNodeInlineFunction(prompt, flow, context) {\n  var messages = [\n    {\n      \"role\": \"system\",\n      \"content\": \"You are a Node-RED expert and are asked to assist with generating code.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"This is the current definition of the current node you need to provide code assistance on: \" + JSON.stringify(flow)\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"Here is some extra context: \" + JSON.stringify(context)\n    },\n    {\n      \"role\": \"user\",\n      \"content\": prompt\n    }\n  ]\n  var schema = {\n    \"type\": \"object\",\n    \"properties\": {\n      \"func\": {\n        \"type\": \"string\",\n        \"description\": \"Javascript code requested by the user.\"\n      },\n      \"outputs\": {\n        \"type\": \"integer\"\n      },\n      \"node_modules\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"var\": {\n              \"type\": \"string\"\n            },\n            \"module\": {\n              \"type\": \"string\"\n            }\n          },\n          \"required\": [\n            \"var\",\n            \"module\"\n          ],\n          \"additionalProperties\": false\n        }\n      }\n    },\n    \"required\": [\n      \"func\",\n      \"outputs\",\n      \"node_modules\"\n    ],\n    \"additionalProperties\": false\n  }\n\n  const response = await fetch(\"https://api.openai.com/v1/chat/completions\", {\n    method: \"POST\",\n    headers: {\n      \"Authorization\": `Bearer ${env.get(\"CREDENTIALS_OPENAI\")}`,\n      \"Content-Type\": \"application/json\"\n    },\n    body: JSON.stringify({\n      max_completion_tokens: 3000,\n      frequency_penalty: 0,\n      presence_penalty: 0,\n      model: \"gpt-5-mini\",\n      reasoning_effort: \"minimal\",\n      messages,\n      response_format: {\n        type: \"json_schema\",\n        json_schema: {\n          name: \"NodeRedFlow\",\n          description: \"Author a Node-RED flow.\",\n          strict: true,\n          schema\n        }\n      }\n    })\n  });\n  if (!response.ok) {\n    const err = await response.json().catch(() => ({}));\n    throw new Error(`OpenAI API error: ${response.status} ${response.statusText}\\n${JSON.stringify(err, null, 2)}`);\n  }\n  const data = await response.json();\n  return JSON.parse(data.choices[0].message.content);\n}\n\nasync function getFlows() {\n  // would rather use RED.nodes.node(msg.payload.transactionId.split(\"-\")[0])\n  const res = await fetch(`${env.get(\"NODERED_URL\")}/auth/token`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({\n      client_id: 'node-red-admin',\n      grant_type: 'password',\n      scope: 'read',\n      username: env.get(\"NODERED_USER\"),\n      password: env.get(\"NODERED_PASS\")\n    })\n  });\n  const { access_token, expires_in, token_type } = await res.json();\n  const flows = await fetch(`${env.get(\"NODERED_URL\")}/flows`, {\n    method: 'GET',\n    headers: {\n      'Authorization': 'Bearer ' + access_token\n    }\n  });\n  const flowsJson = await flows.json();\n  return flowsJson;\n}",
  77.         "outputs": 1,
  78.         "timeout": 0,
  79.         "noerr": 0,
  80.         "initialize": "",
  81.         "finalize": "",
  82.         "libs": [
  83.             {
  84.                 "var": "fetch",
  85.                 "module": "node-fetch"
  86.             },
  87.             {
  88.                 "var": "dayjs",
  89.                 "module": "dayjs"
  90.             }
  91.         ],
  92.         "x": 420,
  93.         "y": 120,
  94.         "wires": [
  95.             [
  96.                 "26a4798f99ba4c7f"
  97.             ]
  98.         ]
  99.     }
  100. ]
Tags: node-red
Advertisement
Add Comment
Please, Sign In to add comment