{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ai-conversation-loop",
  "title": "Ai Conversation Loop",
  "description": "ai conversation timeline, animates infinitely, to showcase your ai product.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://systaliko-ui.vercel.app/r/typing-text"
  ],
  "files": [
    {
      "path": "registry/blocks/ai-conversation-loop/index.tsx",
      "content": "'use client';\n\nimport { TypingText, TypingTextProps } from '@/components/systaliko-ui/text/typing-text';\nimport {\n  motion,\n  AnimatePresence,\n  HTMLMotionProps,\n  MotionConfig,\n} from 'motion/react';\nimport React from 'react';\n\nexport type PhasesT =\n  | 'idle'\n  | 'typing-user'\n  | 'thinking'\n  | 'sources'\n  | 'typing-answer'\n  | 'complete'\n  | 'exiting';\n\nexport interface TimingConfig {\n  pauseBeforeStart: number;\n  thinking: number;\n  sourcesStagger: number;\n  pauseBeforeAnswer: number;\n  holdComplete: number;\n}\n\nconst DEFAULT_TIMING: TimingConfig = {\n  pauseBeforeStart: 400,\n  thinking: 1600,\n  sourcesStagger: 450,\n  pauseBeforeAnswer: 600,\n  holdComplete: 3500,\n};\n\ninterface AiConversationLoopContextType {\n  phase: PhasesT;\n  setPhase: (phase: PhasesT) => void;\n  cycleKey: number;\n}\n\nconst AiConversationLoopContext = React.createContext<\n  AiConversationLoopContextType | undefined\n>(undefined);\n\nexport function useAiConversationLoopContext() {\n  const context = React.useContext(AiConversationLoopContext);\n  if (!context) {\n    throw new Error(\n      'AiConversationLoop components must be used within a <AiConversationLoop>',\n    );\n  }\n  return context;\n}\n\ninterface AiConversationLoopProps extends HTMLMotionProps<'div'> {\n  userTiming?: Partial<TimingConfig>;\n}\n\nexport function AiConversationLoop({\n  userTiming = DEFAULT_TIMING,\n  children,\n  ...props\n}: AiConversationLoopProps) {\n  const [cycleKey, setCycleKey] = React.useState(0);\n  const [phase, setPhase] = React.useState<PhasesT>('idle');\n\n  React.useEffect(() => {\n    let timeoutId: ReturnType<typeof setTimeout>;\n    let intervalId: ReturnType<typeof setInterval>;\n\n    if (phase === 'idle') {\n      timeoutId = setTimeout(() => {\n        setPhase('typing-user');\n      }, userTiming.pauseBeforeStart);\n    } else if (phase === 'thinking') {\n      timeoutId = setTimeout(() => {\n        setPhase('sources');\n      }, userTiming.thinking);\n    } else if (phase === 'sources') {\n      intervalId = setInterval(() => {\n        clearInterval(intervalId);\n        timeoutId = setTimeout(() => {\n          setPhase('typing-answer');\n        }, userTiming.pauseBeforeAnswer);\n      }, userTiming.sourcesStagger);\n    } else if (phase === 'complete') {\n      timeoutId = setTimeout(() => {\n        setPhase('exiting'); // Trigger exit animation\n      }, userTiming.holdComplete);\n    } else if (phase === 'exiting') {\n      timeoutId = setTimeout(() => {\n        setCycleKey((k) => k + 1); // Trigger restart\n        setPhase('idle'); // Enter idle state to pause before restarting\n      }, 500); // Wait for exit animation to complete\n    }\n\n    return () => {\n      clearTimeout(timeoutId);\n      clearInterval(intervalId);\n    };\n  }, [phase, cycleKey, userTiming]);\n\n  return (\n    <AiConversationLoopContext.Provider\n      value={{\n        phase,\n        setPhase,\n        cycleKey,\n      }}\n    >\n      <MotionConfig transition={{ type: 'spring', bounce: 0, duration: 0.5 }}>\n        <AnimatePresence mode=\"wait\">\n          {phase !== 'idle' && phase !== 'exiting' && (\n            <motion.div\n              key={cycleKey}\n              initial={{ opacity: 0, y: 15, filter: 'blur(8px)' }}\n              animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }}\n              exit={{ opacity: 0, y: -15, filter: 'blur(8px)' }}\n              {...props}\n            >\n              {children}\n            </motion.div>\n          )}\n        </AnimatePresence>\n      </MotionConfig>\n    </AiConversationLoopContext.Provider>\n  );\n}\n\nexport function UserMessage({ text, speed, ...props }: TypingTextProps) {\n  const { setPhase, cycleKey } = useAiConversationLoopContext();\n  return (\n    <TypingText\n      key={`user-${cycleKey}`}\n      text={text}\n      speed={speed}\n      onComplete={() => setPhase('thinking')}\n      {...props}\n    />\n  );\n}\n\nexport function AiBlock({ ...props }: HTMLMotionProps<'div'>) {\n  const { phase } = useAiConversationLoopContext();\n\n  return (\n    <motion.div\n      initial={false}\n      animate={phase !== 'typing-user' ? 'visible' : 'hidden'}\n      variants={{\n        hidden: { opacity: 0, y: 10, filter: 'blur(8px)' },\n        visible: {\n          opacity: 1,\n          y: 0,\n          filter: 'blur(0px)',\n          transition: { delay: 0.1 },\n        },\n      }}\n      {...props}\n    />\n  );\n}\n\nexport function AiStatus({ ...props }: HTMLMotionProps<'div'>) {\n  const { phase } = useAiConversationLoopContext();\n  return (\n    <AnimatePresence mode=\"popLayout\">\n      {phase === 'thinking' && (\n        <motion.div\n          key=\"thinking\"\n          layout\n          initial={{ opacity: 0, y: 8, filter: 'blur(4px)' }}\n          animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }}\n          exit={{ opacity: 0, y: -8, filter: 'blur(4px)' }}\n          {...props}\n        />\n      )}\n    </AnimatePresence>\n  );\n}\n\nexport function AiSources({ ...props }: HTMLMotionProps<'div'>) {\n  const { phase } = useAiConversationLoopContext();\n  const isVisible =\n    phase === 'sources' ||\n    phase === 'typing-answer' ||\n    phase === 'complete' ||\n    phase === 'exiting';\n\n  return (\n    <AnimatePresence mode=\"popLayout\">\n      {isVisible && (\n        <motion.div\n          key=\"sources\"\n          layout\n          initial={{ opacity: 0, y: 8, filter: 'blur(4px)' }}\n          animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }}\n          exit={{ opacity: 0, y: -8, filter: 'blur(4px)' }}\n          {...props}\n        />\n      )}\n    </AnimatePresence>\n  );\n}\nexport function AiAnswer({\n  startTyping = true,\n  onTypingComplete,\n  text,\n  speed = 22,\n  children,\n  ...props\n}: TypingTextProps & {\n  startTyping?: boolean;\n  onTypingComplete?: () => void;\n}) {\n  const [isComplete, setIsComplete] = React.useState(false);\n\n  React.useEffect(() => {\n    if (startTyping) setIsComplete(false);\n  }, [startTyping]);\n\n  const handleComplete = React.useCallback(() => {\n    setIsComplete(true);\n    onTypingComplete?.();\n  }, [onTypingComplete]);\n\n  return (\n    <div className=\"overflow-hidden\">\n      <div className=\"rounded-2xl rounded-tl-none border bg-card text-card-foreground px-4 py-3 text-xs shadow-sm\">\n        <p className=\"leading-relaxed\">\n          {startTyping && text && text !== '' ? (\n            <TypingText\n              text={text}\n              speed={speed}\n              onComplete={handleComplete}\n              {...props}\n            />\n          ) : (\n            <>\n              As a Large Language Model, I am unable to provide real-time\n              information.\n            </>\n          )}\n        </p>\n\n        <motion.div\n          initial={false}\n          animate={{ opacity: isComplete ? 1 : 0, y: isComplete ? 0 : 4 }}\n          transition={{ type: 'spring', duration: 0.35, bounce: 0 }}\n          className=\"mt-3 flex items-center justify-between border-t pt-3\"\n        >\n          {children}\n        </motion.div>\n      </div>\n    </div>\n  );\n}\n\nexport function AiResult({\n  text,\n  speed = 22,\n  ...props\n}: TypingTextProps & HTMLMotionProps<'div'>) {\n  const { phase, setPhase } = useAiConversationLoopContext();\n\n  const isVisible =\n    phase === 'typing-answer' || phase === 'complete' || phase === 'exiting';\n  return (\n    <AnimatePresence mode=\"popLayout\">\n      {isVisible && (\n        <motion.div\n          key=\"answer\"\n          layout\n          initial={{ opacity: 0, y: 10, filter: 'blur(4px)' }}\n          animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }}\n          exit={{ opacity: 0, y: -10, filter: 'blur(4px)' }}\n        >\n          <AiAnswer\n            startTyping={\n              phase === 'typing-answer' ||\n              phase === 'complete' ||\n              phase === 'exiting'\n            }\n            text={text}\n            speed={speed}\n            onTypingComplete={() => {\n              if (phase === 'typing-answer') {\n                setPhase('complete');\n              }\n            }}\n            {...props}\n          />\n        </motion.div>\n      )}\n    </AnimatePresence>\n  );\n}\n",
      "type": "registry:block",
      "target": "components/systaliko-ui/ai-conversation-loop.tsx"
    }
  ],
  "type": "registry:block"
}