openAgent
gitlabThe open source UNIVERSAL agent.
clone
https://gitlab.com/bitzzdev/openAgent.gitAIagentautomationopen-sourceuniversal-agent
import type { ColorInput } from "@opentui/core"import { RGBA } from "@opentui/core"import type { ColorGenerator } from "opentui-spinner"interface AdvancedGradientOptions {colors: ColorInput[]trailLength: numberdefaultColor?: ColorInputdirection?: "forward" | "backward" | "bidirectional"holdFrames?: { start?: number; end?: number }enableFading?: booleanminAlpha?: number}interface ScannerState {activePosition: numberisHolding: booleanholdProgress: numberholdTotal: numbermovementProgress: numbermovementTotal: numberisMovingForward: boolean}function getScannerState(frameIndex: number,totalChars: number,options: Pick<AdvancedGradientOptions, "direction" | "holdFrames">,): ScannerState {const { direction = "forward", holdFrames = {} } = optionsif (direction === "bidirectional") {const forwardFrames = totalCharsconst holdEndFrames = holdFrames.end ?? 0const backwardFrames = totalChars - 1if (frameIndex < forwardFrames) {// Moving forwardreturn {activePosition: frameIndex,isHolding: false,holdProgress: 0,holdTotal: 0,movementProgress: frameIndex,movementTotal: forwardFrames,isMovingForward: true,}} else if (frameIndex < forwardFrames + holdEndFrames) {// Holding at endreturn {activePosition: totalChars - 1,isHolding: true,holdProgress: frameIndex - forwardFrames,holdTotal: holdEndFrames,movementProgress: 0,movementTotal: 0,isMovingForward: true,}} else if (frameIndex < forwardFrames + holdEndFrames + backwardFrames) {// Moving backwardconst backwardIndex = frameIndex - forwardFrames - holdEndFramesreturn {activePosition: totalChars - 2 - backwardIndex,isHolding: false,holdProgress: 0,holdTotal: 0,movementProgress: backwardIndex,movementTotal: backwardFrames,isMovingForward: false,}} else {// Holding at startreturn {activePosition: 0,isHolding: true,holdProgress: frameIndex - forwardFrames - holdEndFrames - backwardFrames,holdTotal: holdFrames.start ?? 0,movementProgress: 0,movementTotal: 0,isMovingForward: false,}}} else if (direction === "backward") {return {activePosition: totalChars - 1 - (frameIndex % totalChars),isHolding: false,holdProgress: 0,holdTotal: 0,movementProgress: frameIndex % totalChars,movementTotal: totalChars,isMovingForward: false,}} else {return {activePosition: frameIndex % totalChars,isHolding: false,holdProgress: 0,holdTotal: 0,movementProgress: frameIndex % totalChars,movementTotal: totalChars,isMovingForward: true,}}}function calculateColorIndex(frameIndex: number,charIndex: number,totalChars: number,options: Pick<AdvancedGradientOptions, "direction" | "holdFrames" | "trailLength">,state?: ScannerState,): number {const { trailLength } = optionsconst { activePosition, isHolding, holdProgress, isMovingForward } =state ?? getScannerState(frameIndex, totalChars, options)// Calculate directional distance (positive means trailing behind)const directionalDistance = isMovingForward? activePosition - charIndex // For forward: trail is to the left (lower indices): charIndex - activePosition // For backward: trail is to the right (higher indices)// Handle hold frame fading: keep the lead bright, fade the trailif (isHolding) {// Shift the color index by how long we've been holdingreturn directionalDistance + holdProgress}// Normal movement - show gradient trail only behind the movement directionif (directionalDistance > 0 && directionalDistance < trailLength) {return directionalDistance}// At the active position, show the brightest colorif (directionalDistance === 0) {return 0}return -1}function createKnightRiderTrail(options: AdvancedGradientOptions): ColorGenerator {const { colors, defaultColor, enableFading = true, minAlpha = 0 } = options// Use the provided defaultColor if it's an RGBA instance, otherwise convert/default// We use RGBA.fromHex for the fallback to ensure we have an RGBA object.// Note: If defaultColor is a string, we convert it once here.const defaultRgba = defaultColor instanceof RGBA ? defaultColor : RGBA.fromHex((defaultColor as string) || "#000000")// Store the base alpha from the inactive factorconst baseInactiveAlpha = defaultRgba.alet cachedFrameIndex = -1let cachedState: ScannerState | null = nullreturn (frameIndex: number, charIndex: number, _totalFrames: number, totalChars: number) => {if (frameIndex !== cachedFrameIndex) {cachedFrameIndex = frameIndexcachedState = getScannerState(frameIndex, totalChars, options)}const state = cachedState!const index = calculateColorIndex(frameIndex, charIndex, totalChars, options, state)// Calculate global fade for inactive dots during hold or movementconst { isHolding, holdProgress, holdTotal, movementProgress, movementTotal } = statelet fadeFactor = 1.0if (enableFading) {if (isHolding && holdTotal > 0) {// Fade out linearly to minAlphaconst progress = Math.min(holdProgress / holdTotal, 1)fadeFactor = Math.max(minAlpha, 1 - progress * (1 - minAlpha))} else if (!isHolding && movementTotal > 0) {// Fade in linearly from minAlpha during movementconst progress = Math.min(movementProgress / Math.max(1, movementTotal - 1), 1)fadeFactor = minAlpha + progress * (1 - minAlpha)}}// Combine base inactive alpha with the fade factor// This ensures inactiveFactor is respected while still allowing fading animationdefaultRgba.a = baseInactiveAlpha * fadeFactorif (index === -1) {return defaultRgba}return colors[index] ?? defaultRgba}}/*** Derives a gradient of tail colors from a single bright color using alpha falloff* @param brightColor The brightest color (center/head of the scanner)* @param steps Number of gradient steps (default: 6)* @returns Array of RGBA colors with alpha-based trail fade (background-independent)*/export function deriveTrailColors(brightColor: ColorInput, steps: number = 6): RGBA[] {const baseRgba = brightColor instanceof RGBA ? brightColor : RGBA.fromHex(brightColor as string)const colors: RGBA[] = []for (let i = 0; i < steps; i++) {// Alpha-based falloff with optional bloom effectlet alpha: numberlet brightnessFactor: numberif (i === 0) {// Lead position: full brightness and opacityalpha = 1.0brightnessFactor = 1.0} else if (i === 1) {// Slight bloom/glare effect: brighten color but reduce opacity slightlyalpha = 0.9brightnessFactor = 1.15} else {// Exponential alpha decay for natural-looking trail fadealpha = Math.pow(0.65, i - 1)brightnessFactor = 1.0}const r = Math.min(1.0, baseRgba.r * brightnessFactor)const g = Math.min(1.0, baseRgba.g * brightnessFactor)const b = Math.min(1.0, baseRgba.b * brightnessFactor)colors.push(RGBA.fromValues(r, g, b, alpha))}return colors}/*** Derives the inactive/default color from a bright color using alpha* @param brightColor The brightest color (center/head of the scanner)* @param factor Alpha factor for inactive color (default: 0.2, range: 0-1)* @returns The same color with reduced alpha for background-independent dimming*/export function deriveInactiveColor(brightColor: ColorInput, factor: number = 0.2): RGBA {const baseRgba = brightColor instanceof RGBA ? brightColor : RGBA.fromHex(brightColor as string)// Use the full color brightness but adjust alpha for background-independent dimmingreturn RGBA.fromValues(baseRgba.r, baseRgba.g, baseRgba.b, factor)}export type KnightRiderStyle = "blocks" | "diamonds"export interface KnightRiderOptions {width?: numberstyle?: KnightRiderStyleholdStart?: numberholdEnd?: numbercolors?: ColorInput[]/** Single color to derive trail from (alternative to providing colors array) */color?: ColorInput/** Number of trail steps when using single color (default: 6) */trailSteps?: numberdefaultColor?: ColorInput/** Alpha factor for inactive color when using single color (default: 0.2, range: 0-1) */inactiveFactor?: number/** Enable fading of inactive dots during hold and movement (default: true) */enableFading?: boolean/** Minimum alpha value when fading (default: 0, range: 0-1) */minAlpha?: number}/*** Creates frame strings for a Knight Rider style scanner animation* @param options Configuration options for the Knight Rider effect* @returns Array of frame strings*/export function createFrames(options: KnightRiderOptions = {}): string[] {const width = options.width ?? 8const style = options.style ?? "diamonds"const holdStart = options.holdStart ?? 30const holdEnd = options.holdEnd ?? 9const colors =options.colors ??(options.color? deriveTrailColors(options.color, options.trailSteps): [RGBA.fromHex("#ff0000"), // Brightest Red (Center)RGBA.fromHex("#ff5555"), // Glare/BloomRGBA.fromHex("#dd0000"), // Trail 1RGBA.fromHex("#aa0000"), // Trail 2RGBA.fromHex("#770000"), // Trail 3RGBA.fromHex("#440000"), // Trail 4])const defaultColor =options.defaultColor ??(options.color ? deriveInactiveColor(options.color, options.inactiveFactor) : RGBA.fromHex("#330000"))const trailOptions = {colors,trailLength: colors.length,defaultColor,direction: "bidirectional" as const,holdFrames: { start: holdStart, end: holdEnd },enableFading: options.enableFading,minAlpha: options.minAlpha,}// Bidirectional cycle: Forward (width) + Hold End + Backward (width-1) + Hold Startconst totalFrames = width + holdEnd + (width - 1) + holdStart// Generate dynamic frames where inactive pixels are dots and active ones are blocksconst frames = Array.from({ length: totalFrames }, (_, frameIndex) => {return Array.from({ length: width }, (_, charIndex) => {const index = calculateColorIndex(frameIndex, charIndex, width, trailOptions)if (style === "diamonds") {const shapes = ["⬥", "◆", "⬩", "⬪"]if (index >= 0 && index < trailOptions.colors.length) {return shapes[Math.min(index, shapes.length - 1)]}return "·"}// Default to blocks// It's active if we have a valid color index that is within our colors arrayconst isActive = index >= 0 && index < trailOptions.colors.lengthreturn isActive ? "■" : "⬝"}).join("")})return frames}/*** Creates a color generator function for Knight Rider style scanner animation* @param options Configuration options for the Knight Rider effect* @returns ColorGenerator function*/export function createColors(options: KnightRiderOptions = {}): ColorGenerator {const holdStart = options.holdStart ?? 30const holdEnd = options.holdEnd ?? 9const colors =options.colors ??(options.color? deriveTrailColors(options.color, options.trailSteps): [RGBA.fromHex("#ff0000"), // Brightest Red (Center)RGBA.fromHex("#ff5555"), // Glare/BloomRGBA.fromHex("#dd0000"), // Trail 1RGBA.fromHex("#aa0000"), // Trail 2RGBA.fromHex("#770000"), // Trail 3RGBA.fromHex("#440000"), // Trail 4])const defaultColor =options.defaultColor ??(options.color ? deriveInactiveColor(options.color, options.inactiveFactor) : RGBA.fromHex("#330000"))const trailOptions = {colors,trailLength: colors.length,defaultColor,direction: "bidirectional" as const,holdFrames: { start: holdStart, end: holdEnd },enableFading: options.enableFading,minAlpha: options.minAlpha,}return createKnightRiderTrail(trailOptions)}