You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

utils.ts 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. import { ArrayType, Type } from './types'
  2. import type { ArrayItems, Field, LLMNodeType } from './types'
  3. import type { Schema, ValidationError } from 'jsonschema'
  4. import { Validator } from 'jsonschema'
  5. import produce from 'immer'
  6. import { z } from 'zod'
  7. export const checkNodeValid = (_payload: LLMNodeType) => {
  8. return true
  9. }
  10. export const getFieldType = (field: Field) => {
  11. const { type, items } = field
  12. if(field.schemaType === 'file') return 'file'
  13. if (type !== Type.array || !items)
  14. return type
  15. return ArrayType[items.type as keyof typeof ArrayType]
  16. }
  17. export const getHasChildren = (schema: Field) => {
  18. const complexTypes = [Type.object, Type.array]
  19. if (!complexTypes.includes(schema.type))
  20. return false
  21. if (schema.type === Type.object)
  22. return schema.properties && Object.keys(schema.properties).length > 0
  23. if (schema.type === Type.array)
  24. return schema.items && schema.items.type === Type.object && schema.items.properties && Object.keys(schema.items.properties).length > 0
  25. }
  26. export const getTypeOf = (target: any) => {
  27. if (target === null) return 'null'
  28. if (typeof target !== 'object') {
  29. return typeof target
  30. }
  31. else {
  32. return Object.prototype.toString
  33. .call(target)
  34. .slice(8, -1)
  35. .toLocaleLowerCase()
  36. }
  37. }
  38. export const inferType = (value: any): Type => {
  39. const type = getTypeOf(value)
  40. if (type === 'array') return Type.array
  41. // type boolean will be treated as string
  42. if (type === 'boolean') return Type.string
  43. if (type === 'number') return Type.number
  44. if (type === 'string') return Type.string
  45. if (type === 'object') return Type.object
  46. return Type.string
  47. }
  48. export const jsonToSchema = (json: any): Field => {
  49. const schema: Field = {
  50. type: inferType(json),
  51. }
  52. if (schema.type === Type.object) {
  53. schema.properties = {}
  54. schema.required = []
  55. schema.additionalProperties = false
  56. Object.entries(json).forEach(([key, value]) => {
  57. schema.properties![key] = jsonToSchema(value)
  58. schema.required!.push(key)
  59. })
  60. }
  61. else if (schema.type === Type.array) {
  62. schema.items = jsonToSchema(json[0]) as ArrayItems
  63. }
  64. return schema
  65. }
  66. export const checkJsonDepth = (json: any) => {
  67. if (!json || getTypeOf(json) !== 'object')
  68. return 0
  69. let maxDepth = 0
  70. if (getTypeOf(json) === 'array') {
  71. if (json[0] && getTypeOf(json[0]) === 'object')
  72. maxDepth = checkJsonDepth(json[0])
  73. }
  74. else if (getTypeOf(json) === 'object') {
  75. const propertyDepths = Object.values(json).map(value => checkJsonDepth(value))
  76. maxDepth = propertyDepths.length ? Math.max(...propertyDepths) + 1 : 1
  77. }
  78. return maxDepth
  79. }
  80. export const checkJsonSchemaDepth = (schema: Field) => {
  81. if (!schema || getTypeOf(schema) !== 'object')
  82. return 0
  83. let maxDepth = 0
  84. if (schema.type === Type.object && schema.properties) {
  85. const propertyDepths = Object.values(schema.properties).map(value => checkJsonSchemaDepth(value))
  86. maxDepth = propertyDepths.length ? Math.max(...propertyDepths) + 1 : 1
  87. }
  88. else if (schema.type === Type.array && schema.items && schema.items.type === Type.object) {
  89. maxDepth = checkJsonSchemaDepth(schema.items) + 1
  90. }
  91. return maxDepth
  92. }
  93. export const findPropertyWithPath = (target: any, path: string[]) => {
  94. let current = target
  95. for (const key of path)
  96. current = current[key]
  97. return current
  98. }
  99. const draft07MetaSchema = {
  100. $schema: 'http://json-schema.org/draft-07/schema#',
  101. $id: 'http://json-schema.org/draft-07/schema#',
  102. title: 'Core schema meta-schema',
  103. definitions: {
  104. schemaArray: {
  105. type: 'array',
  106. minItems: 1,
  107. items: { $ref: '#' },
  108. },
  109. nonNegativeInteger: {
  110. type: 'integer',
  111. minimum: 0,
  112. },
  113. nonNegativeIntegerDefault0: {
  114. allOf: [
  115. { $ref: '#/definitions/nonNegativeInteger' },
  116. { default: 0 },
  117. ],
  118. },
  119. simpleTypes: {
  120. enum: [
  121. 'array',
  122. 'boolean',
  123. 'integer',
  124. 'null',
  125. 'number',
  126. 'object',
  127. 'string',
  128. ],
  129. },
  130. stringArray: {
  131. type: 'array',
  132. items: { type: 'string' },
  133. uniqueItems: true,
  134. default: [],
  135. },
  136. },
  137. type: ['object', 'boolean'],
  138. properties: {
  139. $id: {
  140. type: 'string',
  141. format: 'uri-reference',
  142. },
  143. $schema: {
  144. type: 'string',
  145. format: 'uri',
  146. },
  147. $ref: {
  148. type: 'string',
  149. format: 'uri-reference',
  150. },
  151. title: {
  152. type: 'string',
  153. },
  154. description: {
  155. type: 'string',
  156. },
  157. default: true,
  158. readOnly: {
  159. type: 'boolean',
  160. default: false,
  161. },
  162. examples: {
  163. type: 'array',
  164. items: true,
  165. },
  166. multipleOf: {
  167. type: 'number',
  168. exclusiveMinimum: 0,
  169. },
  170. maximum: {
  171. type: 'number',
  172. },
  173. exclusiveMaximum: {
  174. type: 'number',
  175. },
  176. minimum: {
  177. type: 'number',
  178. },
  179. exclusiveMinimum: {
  180. type: 'number',
  181. },
  182. maxLength: { $ref: '#/definitions/nonNegativeInteger' },
  183. minLength: { $ref: '#/definitions/nonNegativeIntegerDefault0' },
  184. pattern: {
  185. type: 'string',
  186. format: 'regex',
  187. },
  188. additionalItems: { $ref: '#' },
  189. items: {
  190. anyOf: [
  191. { $ref: '#' },
  192. { $ref: '#/definitions/schemaArray' },
  193. ],
  194. default: true,
  195. },
  196. maxItems: { $ref: '#/definitions/nonNegativeInteger' },
  197. minItems: { $ref: '#/definitions/nonNegativeIntegerDefault0' },
  198. uniqueItems: {
  199. type: 'boolean',
  200. default: false,
  201. },
  202. contains: { $ref: '#' },
  203. maxProperties: { $ref: '#/definitions/nonNegativeInteger' },
  204. minProperties: { $ref: '#/definitions/nonNegativeIntegerDefault0' },
  205. required: { $ref: '#/definitions/stringArray' },
  206. additionalProperties: { $ref: '#' },
  207. definitions: {
  208. type: 'object',
  209. additionalProperties: { $ref: '#' },
  210. default: {},
  211. },
  212. properties: {
  213. type: 'object',
  214. additionalProperties: { $ref: '#' },
  215. default: {},
  216. },
  217. patternProperties: {
  218. type: 'object',
  219. additionalProperties: { $ref: '#' },
  220. propertyNames: { format: 'regex' },
  221. default: {},
  222. },
  223. dependencies: {
  224. type: 'object',
  225. additionalProperties: {
  226. anyOf: [
  227. { $ref: '#' },
  228. { $ref: '#/definitions/stringArray' },
  229. ],
  230. },
  231. },
  232. propertyNames: { $ref: '#' },
  233. const: true,
  234. enum: {
  235. type: 'array',
  236. items: true,
  237. minItems: 1,
  238. uniqueItems: true,
  239. },
  240. type: {
  241. anyOf: [
  242. { $ref: '#/definitions/simpleTypes' },
  243. {
  244. type: 'array',
  245. items: { $ref: '#/definitions/simpleTypes' },
  246. minItems: 1,
  247. uniqueItems: true,
  248. },
  249. ],
  250. },
  251. format: { type: 'string' },
  252. allOf: { $ref: '#/definitions/schemaArray' },
  253. anyOf: { $ref: '#/definitions/schemaArray' },
  254. oneOf: { $ref: '#/definitions/schemaArray' },
  255. not: { $ref: '#' },
  256. },
  257. default: true,
  258. } as unknown as Schema
  259. const validator = new Validator()
  260. export const validateSchemaAgainstDraft7 = (schemaToValidate: any) => {
  261. const schema = produce(schemaToValidate, (draft: any) => {
  262. // Make sure the schema has the $schema property for draft-07
  263. if (!draft.$schema)
  264. draft.$schema = 'http://json-schema.org/draft-07/schema#'
  265. })
  266. const result = validator.validate(schema, draft07MetaSchema, {
  267. nestedErrors: true,
  268. throwError: false,
  269. })
  270. // Access errors from the validation result
  271. const errors = result.valid ? [] : result.errors || []
  272. return errors
  273. }
  274. export const getValidationErrorMessage = (errors: ValidationError[]) => {
  275. const message = errors.map((error) => {
  276. return `Error: ${error.path.join('.')} ${error.message} Details: ${JSON.stringify(error.stack)}`
  277. }).join('; ')
  278. return message
  279. }
  280. // Previous Not support boolean type, so transform boolean to string when paste it into schema editor
  281. export const convertBooleanToString = (schema: any) => {
  282. if (schema.type === Type.boolean)
  283. schema.type = Type.string
  284. if (schema.type === Type.array && schema.items && schema.items.type === Type.boolean)
  285. schema.items.type = Type.string
  286. if (schema.type === Type.object) {
  287. schema.properties = Object.entries(schema.properties).reduce((acc, [key, value]) => {
  288. acc[key] = convertBooleanToString(value)
  289. return acc
  290. }, {} as any)
  291. }
  292. if (schema.type === Type.array && schema.items && schema.items.type === Type.object) {
  293. schema.items.properties = Object.entries(schema.items.properties).reduce((acc, [key, value]) => {
  294. acc[key] = convertBooleanToString(value)
  295. return acc
  296. }, {} as any)
  297. }
  298. return schema
  299. }
  300. const schemaRootObject = z.object({
  301. type: z.literal('object'),
  302. properties: z.record(z.string(), z.any()),
  303. required: z.array(z.string()),
  304. additionalProperties: z.boolean().optional(),
  305. })
  306. export const preValidateSchema = (schema: any) => {
  307. const result = schemaRootObject.safeParse(schema)
  308. return result
  309. }