mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
Implement CLI output message type for displaying command output from user/assistant messages. Adds CliOutputBlock component and type with detection logic based on message metadata and CLI tags. Includes merging of adjacent CLI output blocks for cleaner presentation. Enhance layout throughout components with proper overflow handling and width constraints for improved text wrapping and scrolling behavior.
145 lines
4.2 KiB
TypeScript
145 lines
4.2 KiB
TypeScript
import React, { useRef, useState, useEffect, useMemo } from 'react'
|
|
import { MarkdownRenderer } from '@/components/MarkdownRenderer'
|
|
|
|
// 特效单词列表 - 可以轻松扩展
|
|
const RAINBOW_WORDS = [
|
|
'ultrathink',
|
|
'fuck',
|
|
'step by step',
|
|
'ELI5',
|
|
'lgtm',
|
|
'impl it',
|
|
'pls fix',
|
|
'stop changing',
|
|
'用中文',
|
|
'我说了',
|
|
'别又',
|
|
'为什么又',
|
|
'根本不',
|
|
'还是报错',
|
|
'大哥',
|
|
'求你',
|
|
'就改这里',
|
|
'弱智',
|
|
]
|
|
|
|
// 转义正则特殊字符
|
|
function escapeRegExp(str: string): string {
|
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
}
|
|
|
|
// 动态构建正则表达式
|
|
function buildPattern(words: string[]): RegExp {
|
|
const pattern = words.map(escapeRegExp).join('|')
|
|
return new RegExp(`(${pattern})`, 'gi')
|
|
}
|
|
|
|
// 快速检查是否包含任何特效单词
|
|
function hasAnySpecialWord(text: string, words: string[]): boolean {
|
|
const lowerText = text.toLowerCase()
|
|
return words.some(word => lowerText.includes(word.toLowerCase()))
|
|
}
|
|
|
|
const RAINBOW_PATTERN = buildPattern(RAINBOW_WORDS)
|
|
|
|
// Each letter gets a different delay for wave effect
|
|
function RainbowWord({ word, baseKey }: { word: string; baseKey: number }) {
|
|
const totalLetters = word.length
|
|
const cycleDuration = 2 // seconds for sparkle to travel across all letters
|
|
|
|
return (
|
|
<span>
|
|
{word.split('').map((letter, i) => {
|
|
// Each letter has a different delay to create wave effect
|
|
const colorDelay = (i / totalLetters) * 2 // stagger rainbow colors
|
|
const sparkleDelay = (i / totalLetters) * cycleDuration // sparkle wave
|
|
|
|
return (
|
|
<span
|
|
key={`${baseKey}-${i}`}
|
|
className="rainbow-letter"
|
|
style={{
|
|
animationDelay: `${-colorDelay}s, ${-sparkleDelay}s`,
|
|
}}
|
|
>
|
|
{letter === ' ' ? '\u00A0' : letter}
|
|
</span>
|
|
)
|
|
})}
|
|
</span>
|
|
)
|
|
}
|
|
|
|
// Process text string to wrap special words with RainbowWord
|
|
function processTextForRainbow(text: string): React.ReactNode {
|
|
RAINBOW_PATTERN.lastIndex = 0
|
|
const parts: React.ReactNode[] = []
|
|
let lastIndex = 0
|
|
let match: RegExpExecArray | null
|
|
|
|
while ((match = RAINBOW_PATTERN.exec(text)) !== null) {
|
|
if (match.index > lastIndex) {
|
|
parts.push(text.slice(lastIndex, match.index))
|
|
}
|
|
parts.push(<RainbowWord key={match.index} word={match[1]} baseKey={match.index} />)
|
|
lastIndex = match.index + match[0].length
|
|
}
|
|
|
|
if (lastIndex < text.length) {
|
|
parts.push(text.slice(lastIndex))
|
|
}
|
|
|
|
return <>{parts}</>
|
|
}
|
|
|
|
// Process React children to apply rainbow to text nodes
|
|
function processChildrenForRainbow(children: React.ReactNode): React.ReactNode {
|
|
return React.Children.map(children, (child) => {
|
|
if (typeof child === 'string') {
|
|
return processTextForRainbow(child)
|
|
}
|
|
return child
|
|
})
|
|
}
|
|
|
|
export function LazyRainbowText(props: { text: string }) {
|
|
const text = props.text
|
|
const ref = useRef<HTMLDivElement>(null)
|
|
const [hasBeenVisible, setHasBeenVisible] = useState(false)
|
|
|
|
useEffect(() => {
|
|
const el = ref.current
|
|
if (!el) return
|
|
|
|
const observer = new IntersectionObserver(
|
|
([entry]) => {
|
|
if (entry.isIntersecting) {
|
|
setHasBeenVisible(true)
|
|
}
|
|
},
|
|
{ rootMargin: '100px' }
|
|
)
|
|
|
|
observer.observe(el)
|
|
return () => observer.disconnect()
|
|
}, [])
|
|
|
|
// Quick check: if no special words, just render markdown
|
|
const hasSpecialWord = hasAnySpecialWord(text, RAINBOW_WORDS)
|
|
|
|
const rainbowComponents = useMemo(() => ({
|
|
p: ({ children }: { children?: React.ReactNode }) => (
|
|
<p>{processChildrenForRainbow(children)}</p>
|
|
),
|
|
}), [])
|
|
|
|
return (
|
|
<div ref={ref}>
|
|
<MarkdownRenderer
|
|
content={text}
|
|
components={hasSpecialWord && hasBeenVisible ? rainbowComponents : undefined}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|