 85bf1341f3
			
		
	
	85bf1341f3
	
	
	
		
			
			Frontend Enhancements: - Complete React TypeScript frontend with modern UI components - Distributed workflows management interface with real-time updates - Socket.IO integration for live agent status monitoring - Agent management dashboard with cluster visualization - Project management interface with metrics and task tracking - Responsive design with proper error handling and loading states Backend Infrastructure: - Distributed coordinator for multi-agent workflow orchestration - Cluster management API with comprehensive agent operations - Enhanced database models for agents and projects - Project service for filesystem-based project discovery - Performance monitoring and metrics collection - Comprehensive API documentation and error handling Documentation: - Complete distributed development guide (README_DISTRIBUTED.md) - Comprehensive development report with architecture insights - System configuration templates and deployment guides The platform now provides a complete web interface for managing the distributed AI cluster with real-time monitoring, workflow orchestration, and agent coordination capabilities. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			449 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			449 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| let range = require('normalize-range')
 | |
| let parser = require('postcss-value-parser')
 | |
| 
 | |
| let OldValue = require('../old-value')
 | |
| let utils = require('../utils')
 | |
| let Value = require('../value')
 | |
| 
 | |
| let IS_DIRECTION = /top|left|right|bottom/gi
 | |
| 
 | |
| class Gradient extends Value {
 | |
|   /**
 | |
|    * Do not add non-webkit prefixes for list-style and object
 | |
|    */
 | |
|   add(decl, prefix) {
 | |
|     let p = decl.prop
 | |
|     if (p.includes('mask')) {
 | |
|       if (prefix === '-webkit-' || prefix === '-webkit- old') {
 | |
|         return super.add(decl, prefix)
 | |
|       }
 | |
|     } else if (
 | |
|       p === 'list-style' ||
 | |
|       p === 'list-style-image' ||
 | |
|       p === 'content'
 | |
|     ) {
 | |
|       if (prefix === '-webkit-' || prefix === '-webkit- old') {
 | |
|         return super.add(decl, prefix)
 | |
|       }
 | |
|     } else {
 | |
|       return super.add(decl, prefix)
 | |
|     }
 | |
|     return undefined
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get div token from exists parameters
 | |
|    */
 | |
|   cloneDiv(params) {
 | |
|     for (let i of params) {
 | |
|       if (i.type === 'div' && i.value === ',') {
 | |
|         return i
 | |
|       }
 | |
|     }
 | |
|     return { after: ' ', type: 'div', value: ',' }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Change colors syntax to old webkit
 | |
|    */
 | |
|   colorStops(params) {
 | |
|     let result = []
 | |
|     for (let i = 0; i < params.length; i++) {
 | |
|       let pos
 | |
|       let param = params[i]
 | |
|       let item
 | |
|       if (i === 0) {
 | |
|         continue
 | |
|       }
 | |
| 
 | |
|       let color = parser.stringify(param[0])
 | |
|       if (param[1] && param[1].type === 'word') {
 | |
|         pos = param[1].value
 | |
|       } else if (param[2] && param[2].type === 'word') {
 | |
|         pos = param[2].value
 | |
|       }
 | |
| 
 | |
|       let stop
 | |
|       if (i === 1 && (!pos || pos === '0%')) {
 | |
|         stop = `from(${color})`
 | |
|       } else if (i === params.length - 1 && (!pos || pos === '100%')) {
 | |
|         stop = `to(${color})`
 | |
|       } else if (pos) {
 | |
|         stop = `color-stop(${pos}, ${color})`
 | |
|       } else {
 | |
|         stop = `color-stop(${color})`
 | |
|       }
 | |
| 
 | |
|       let div = param[param.length - 1]
 | |
|       params[i] = [{ type: 'word', value: stop }]
 | |
|       if (div.type === 'div' && div.value === ',') {
 | |
|         item = params[i].push(div)
 | |
|       }
 | |
|       result.push(item)
 | |
|     }
 | |
|     return result
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Change new direction to old
 | |
|    */
 | |
|   convertDirection(params) {
 | |
|     if (params.length > 0) {
 | |
|       if (params[0].value === 'to') {
 | |
|         this.fixDirection(params)
 | |
|       } else if (params[0].value.includes('deg')) {
 | |
|         this.fixAngle(params)
 | |
|       } else if (this.isRadial(params)) {
 | |
|         this.fixRadial(params)
 | |
|       }
 | |
|     }
 | |
|     return params
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Add 90 degrees
 | |
|    */
 | |
|   fixAngle(params) {
 | |
|     let first = params[0].value
 | |
|     first = parseFloat(first)
 | |
|     first = Math.abs(450 - first) % 360
 | |
|     first = this.roundFloat(first, 3)
 | |
|     params[0].value = `${first}deg`
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Replace `to top left` to `bottom right`
 | |
|    */
 | |
|   fixDirection(params) {
 | |
|     params.splice(0, 2)
 | |
| 
 | |
|     for (let param of params) {
 | |
|       if (param.type === 'div') {
 | |
|         break
 | |
|       }
 | |
|       if (param.type === 'word') {
 | |
|         param.value = this.revertDirection(param.value)
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Fix radial direction syntax
 | |
|    */
 | |
|   fixRadial(params) {
 | |
|     let first = []
 | |
|     let second = []
 | |
|     let a, b, c, i, next
 | |
| 
 | |
|     for (i = 0; i < params.length - 2; i++) {
 | |
|       a = params[i]
 | |
|       b = params[i + 1]
 | |
|       c = params[i + 2]
 | |
|       if (a.type === 'space' && b.value === 'at' && c.type === 'space') {
 | |
|         next = i + 3
 | |
|         break
 | |
|       } else {
 | |
|         first.push(a)
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let div
 | |
|     for (i = next; i < params.length; i++) {
 | |
|       if (params[i].type === 'div') {
 | |
|         div = params[i]
 | |
|         break
 | |
|       } else {
 | |
|         second.push(params[i])
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     params.splice(0, i, ...second, div, ...first)
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Look for at word
 | |
|    */
 | |
|   isRadial(params) {
 | |
|     let state = 'before'
 | |
|     for (let param of params) {
 | |
|       if (state === 'before' && param.type === 'space') {
 | |
|         state = 'at'
 | |
|       } else if (state === 'at' && param.value === 'at') {
 | |
|         state = 'after'
 | |
|       } else if (state === 'after' && param.type === 'space') {
 | |
|         return true
 | |
|       } else if (param.type === 'div') {
 | |
|         break
 | |
|       } else {
 | |
|         state = 'before'
 | |
|       }
 | |
|     }
 | |
|     return false
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Replace old direction to new
 | |
|    */
 | |
|   newDirection(params) {
 | |
|     if (params[0].value === 'to') {
 | |
|       return params
 | |
|     }
 | |
|     IS_DIRECTION.lastIndex = 0 // reset search index of global regexp
 | |
|     if (!IS_DIRECTION.test(params[0].value)) {
 | |
|       return params
 | |
|     }
 | |
| 
 | |
|     params.unshift(
 | |
|       {
 | |
|         type: 'word',
 | |
|         value: 'to'
 | |
|       },
 | |
|       {
 | |
|         type: 'space',
 | |
|         value: ' '
 | |
|       }
 | |
|     )
 | |
| 
 | |
|     for (let i = 2; i < params.length; i++) {
 | |
|       if (params[i].type === 'div') {
 | |
|         break
 | |
|       }
 | |
|       if (params[i].type === 'word') {
 | |
|         params[i].value = this.revertDirection(params[i].value)
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return params
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Normalize angle
 | |
|    */
 | |
|   normalize(nodes, gradientName) {
 | |
|     if (!nodes[0]) return nodes
 | |
| 
 | |
|     if (/-?\d+(.\d+)?grad/.test(nodes[0].value)) {
 | |
|       nodes[0].value = this.normalizeUnit(nodes[0].value, 400)
 | |
|     } else if (/-?\d+(.\d+)?rad/.test(nodes[0].value)) {
 | |
|       nodes[0].value = this.normalizeUnit(nodes[0].value, 2 * Math.PI)
 | |
|     } else if (/-?\d+(.\d+)?turn/.test(nodes[0].value)) {
 | |
|       nodes[0].value = this.normalizeUnit(nodes[0].value, 1)
 | |
|     } else if (nodes[0].value.includes('deg')) {
 | |
|       let num = parseFloat(nodes[0].value)
 | |
|       num = range.wrap(0, 360, num)
 | |
|       nodes[0].value = `${num}deg`
 | |
|     }
 | |
| 
 | |
|     if (
 | |
|       gradientName === 'linear-gradient' ||
 | |
|       gradientName === 'repeating-linear-gradient'
 | |
|     ) {
 | |
|       let direction = nodes[0].value
 | |
| 
 | |
|       // Unitless zero for `<angle>` values are allowed in CSS gradients and transforms.
 | |
|       // Spec: https://github.com/w3c/csswg-drafts/commit/602789171429b2231223ab1e5acf8f7f11652eb3
 | |
|       if (direction === '0deg' || direction === '0') {
 | |
|         nodes = this.replaceFirst(nodes, 'to', ' ', 'top')
 | |
|       } else if (direction === '90deg') {
 | |
|         nodes = this.replaceFirst(nodes, 'to', ' ', 'right')
 | |
|       } else if (direction === '180deg') {
 | |
|         nodes = this.replaceFirst(nodes, 'to', ' ', 'bottom') // default value
 | |
|       } else if (direction === '270deg') {
 | |
|         nodes = this.replaceFirst(nodes, 'to', ' ', 'left')
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return nodes
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Convert angle unit to deg
 | |
|    */
 | |
|   normalizeUnit(str, full) {
 | |
|     let num = parseFloat(str)
 | |
|     let deg = (num / full) * 360
 | |
|     return `${deg}deg`
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Remove old WebKit gradient too
 | |
|    */
 | |
|   old(prefix) {
 | |
|     if (prefix === '-webkit-') {
 | |
|       let type
 | |
|       if (this.name === 'linear-gradient') {
 | |
|         type = 'linear'
 | |
|       } else if (this.name === 'repeating-linear-gradient') {
 | |
|         type = 'repeating-linear'
 | |
|       } else if (this.name === 'repeating-radial-gradient') {
 | |
|         type = 'repeating-radial'
 | |
|       } else {
 | |
|         type = 'radial'
 | |
|       }
 | |
|       let string = '-gradient'
 | |
|       let regexp = utils.regexp(
 | |
|         `-webkit-(${type}-gradient|gradient\\(\\s*${type})`,
 | |
|         false
 | |
|       )
 | |
| 
 | |
|       return new OldValue(this.name, prefix + this.name, string, regexp)
 | |
|     } else {
 | |
|       return super.old(prefix)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Change direction syntax to old webkit
 | |
|    */
 | |
|   oldDirection(params) {
 | |
|     let div = this.cloneDiv(params[0])
 | |
| 
 | |
|     if (params[0][0].value !== 'to') {
 | |
|       return params.unshift([
 | |
|         { type: 'word', value: Gradient.oldDirections.bottom },
 | |
|         div
 | |
|       ])
 | |
|     } else {
 | |
|       let words = []
 | |
|       for (let node of params[0].slice(2)) {
 | |
|         if (node.type === 'word') {
 | |
|           words.push(node.value.toLowerCase())
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       words = words.join(' ')
 | |
|       let old = Gradient.oldDirections[words] || words
 | |
| 
 | |
|       params[0] = [{ type: 'word', value: old }, div]
 | |
|       return params[0]
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Convert to old webkit syntax
 | |
|    */
 | |
|   oldWebkit(node) {
 | |
|     let { nodes } = node
 | |
|     let string = parser.stringify(node.nodes)
 | |
| 
 | |
|     if (this.name !== 'linear-gradient') {
 | |
|       return false
 | |
|     }
 | |
|     if (nodes[0] && nodes[0].value.includes('deg')) {
 | |
|       return false
 | |
|     }
 | |
|     if (
 | |
|       string.includes('px') ||
 | |
|       string.includes('-corner') ||
 | |
|       string.includes('-side')
 | |
|     ) {
 | |
|       return false
 | |
|     }
 | |
| 
 | |
|     let params = [[]]
 | |
|     for (let i of nodes) {
 | |
|       params[params.length - 1].push(i)
 | |
|       if (i.type === 'div' && i.value === ',') {
 | |
|         params.push([])
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     this.oldDirection(params)
 | |
|     this.colorStops(params)
 | |
| 
 | |
|     node.nodes = []
 | |
|     for (let param of params) {
 | |
|       node.nodes = node.nodes.concat(param)
 | |
|     }
 | |
| 
 | |
|     node.nodes.unshift(
 | |
|       { type: 'word', value: 'linear' },
 | |
|       this.cloneDiv(node.nodes)
 | |
|     )
 | |
|     node.value = '-webkit-gradient'
 | |
| 
 | |
|     return true
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Change degrees for webkit prefix
 | |
|    */
 | |
|   replace(string, prefix) {
 | |
|     let ast = parser(string)
 | |
|     for (let node of ast.nodes) {
 | |
|       let gradientName = this.name // gradient name
 | |
|       if (node.type === 'function' && node.value === gradientName) {
 | |
|         node.nodes = this.newDirection(node.nodes)
 | |
|         node.nodes = this.normalize(node.nodes, gradientName)
 | |
|         if (prefix === '-webkit- old') {
 | |
|           let changes = this.oldWebkit(node)
 | |
|           if (!changes) {
 | |
|             return false
 | |
|           }
 | |
|         } else {
 | |
|           node.nodes = this.convertDirection(node.nodes)
 | |
|           node.value = prefix + node.value
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     return ast.toString()
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Replace first token
 | |
|    */
 | |
|   replaceFirst(params, ...words) {
 | |
|     let prefix = words.map(i => {
 | |
|       if (i === ' ') {
 | |
|         return { type: 'space', value: i }
 | |
|       }
 | |
|       return { type: 'word', value: i }
 | |
|     })
 | |
|     return prefix.concat(params.slice(1))
 | |
|   }
 | |
| 
 | |
|   revertDirection(word) {
 | |
|     return Gradient.directions[word.toLowerCase()] || word
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Round float and save digits under dot
 | |
|    */
 | |
|   roundFloat(float, digits) {
 | |
|     return parseFloat(float.toFixed(digits))
 | |
|   }
 | |
| }
 | |
| 
 | |
| Gradient.names = [
 | |
|   'linear-gradient',
 | |
|   'repeating-linear-gradient',
 | |
|   'radial-gradient',
 | |
|   'repeating-radial-gradient'
 | |
| ]
 | |
| 
 | |
| Gradient.directions = {
 | |
|   bottom: 'top',
 | |
|   left: 'right',
 | |
|   right: 'left',
 | |
|   top: 'bottom' // default value
 | |
| }
 | |
| 
 | |
| // Direction to replace
 | |
| Gradient.oldDirections = {
 | |
|   'bottom': 'left top, left bottom',
 | |
|   'bottom left': 'right top, left bottom',
 | |
|   'bottom right': 'left top, right bottom',
 | |
|   'left': 'right top, left top',
 | |
| 
 | |
|   'left bottom': 'right top, left bottom',
 | |
|   'left top': 'right bottom, left top',
 | |
|   'right': 'left top, right top',
 | |
|   'right bottom': 'left top, right bottom',
 | |
|   'right top': 'left bottom, right top',
 | |
|   'top': 'left bottom, left top',
 | |
|   'top left': 'right bottom, left top',
 | |
|   'top right': 'left bottom, right top'
 | |
| }
 | |
| 
 | |
| module.exports = Gradient
 |