init: initial commit

This commit is contained in:
Blizzard
2026-04-01 14:09:33 +08:00
commit aef2e152dc
66 changed files with 6540 additions and 0 deletions
+136
View File
@@ -0,0 +1,136 @@
import { useState } from 'react';
interface AIPanelProps {
text: string;
loading: boolean;
error: string | null;
isFallback: boolean;
question: string;
onCopy: (text: string) => void;
onStop: () => void;
onClose: () => void;
}
export default function AIPanel({
text, loading, error, isFallback, question, onCopy, onStop, onClose,
}: AIPanelProps) {
const [copied, setCopied] = useState(false);
const handleCopy = () => {
onCopy(text);
setCopied(true);
setTimeout(() => setCopied(false), 1000);
};
return (
<div className="flex-shrink-0 px-4 pb-4 animate-fade-in">
{/* Header */}
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-1.5">
<span className="text-sm"></span>
<span className="text-[12px] font-semibold text-accent-light">
DeepSeek RAG
</span>
{/* Animated loading dots */}
{loading && (
<div className="flex gap-1 ml-1">
{[0, 1, 2].map(i => (
<span
key={i}
className="w-1 h-1 rounded-full bg-accent-light"
style={{ animation: `pulseDot 1.4s ease-in-out ${i * 0.2}s infinite` }}
/>
))}
</div>
)}
{/* Fallback badge */}
{isFallback && !loading && (
<span className="text-[9px] px-1.5 py-0.5 rounded-full bg-amber-500/20 text-amber-400 border border-amber-500/30 ml-1">
</span>
)}
</div>
<div className="flex items-center gap-1">
{/* Stop button — only visible while streaming */}
{loading && (
<button
id="btn-ai-stop"
onClick={onStop}
title="停止生成"
className="flex items-center gap-1 text-[10px] px-2 py-1 rounded-md
bg-red-500/15 text-red-400 border border-red-500/25
hover:bg-red-500/25 transition-all"
>
<span></span>
</button>
)}
<button
id="btn-ai-close"
onClick={onClose}
className="text-white/30 hover:text-white/70 text-xs transition-colors ml-1"
>
</button>
</div>
</div>
{/* Question context */}
{question && (
<p className="text-[10px] text-white/35 mb-2 line-clamp-1">
{question}
</p>
)}
{/* Fallback notice */}
{isFallback && (
<div className="mb-2 px-2 py-1.5 rounded-lg bg-amber-500/10 border border-amber-500/20
text-amber-400 text-[10px] flex items-center gap-1.5">
<span></span>
<span>DeepSeek </span>
</div>
)}
{/* Error state */}
{error && !isFallback ? (
<div className="ai-panel border-red-500/30 bg-red-500/5">
<p className="text-red-400 text-[12px] font-medium mb-1"> AI </p>
<p className="text-white/50 text-[11px]">{error}</p>
<p className="text-white/30 text-[11px] mt-2">使</p>
</div>
) : (
/* Content panel with typewriter effect */
<div className={`ai-panel ${isFallback ? 'border-amber-500/20 bg-amber-500/5' : ''}`}>
{loading && !text ? (
<p className="text-white/30 text-[12px] animate-pulse">
DeepSeek
</p>
) : (
<p className="text-[13px] leading-relaxed whitespace-pre-wrap">
{text}
{/* Blinking cursor while streaming */}
{loading && (
<span className="inline-block w-0.5 h-3.5 bg-accent ml-0.5 align-middle"
style={{ animation: 'pulseDot 0.8s ease-in-out infinite' }} />
)}
</p>
)}
</div>
)}
{/* Copy button */}
{text && !error && (
<button
id="btn-ai-copy"
onClick={handleCopy}
className="mt-2 w-full text-[11px] py-2 rounded-lg bg-accent/20 text-accent-light
border border-accent/30 hover:bg-accent/35 transition-all font-medium"
>
{copied ? '✓ 已复制' : '📋 复制此话术'}
</button>
)}
</div>
);
}