116 lines
5.1 KiB
TypeScript
116 lines
5.1 KiB
TypeScript
'use client';
|
|
|
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
import { Sparkles, Send, RefreshCw, BookOpen, Bot, ArrowLeft, BrainCircuit, Zap } from 'lucide-react';
|
|
import { ChatMessage, UserUsage } from '@/lib/types';
|
|
import { useLanguage } from '@/providers/LanguageProvider';
|
|
|
|
interface AIPanelProps {
|
|
chatHistory: ChatMessage[];
|
|
onSendMessage: (msg: string) => void;
|
|
onInsertText: (text: string) => void;
|
|
selectedText: string;
|
|
isGenerating: boolean;
|
|
usage?: UserUsage;
|
|
}
|
|
|
|
const AIPanel: React.FC<AIPanelProps> = ({ chatHistory, onSendMessage, onInsertText, selectedText, isGenerating, usage }) => {
|
|
const { t } = useLanguage();
|
|
const [input, setInput] = useState("");
|
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
}, [chatHistory, isGenerating]);
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!input.trim() || isGenerating) return;
|
|
onSendMessage(input);
|
|
setInput("");
|
|
};
|
|
|
|
const isLimitReached = usage ? usage.aiActionsCurrent >= usage.aiActionsLimit : false;
|
|
|
|
return (
|
|
<div className="flex flex-col h-full bg-theme-panel border-l border-theme-border shadow-xl w-80 lg:w-96 transition-colors duration-300">
|
|
{/* Header with Usage Counter */}
|
|
<div className="p-4 bg-indigo-600 text-white flex items-center justify-between shadow-md">
|
|
<div className="flex items-center gap-2">
|
|
<Sparkles size={20} className="animate-pulse" />
|
|
<h3 className="font-bold tracking-tight">{t('ai_panel.title')}</h3>
|
|
</div>
|
|
{usage && (
|
|
<div className="bg-indigo-900/50 px-2 py-1 rounded text-[10px] font-black flex items-center gap-1">
|
|
<Zap size={10} fill="currentColor" /> {usage.aiActionsCurrent} / {usage.aiActionsLimit === 999999 ? '∞' : usage.aiActionsLimit}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{selectedText && (
|
|
<div className="bg-indigo-50 p-3 border-b border-indigo-100 text-xs text-indigo-800">
|
|
<div className="font-bold flex items-center gap-1 mb-1"><BookOpen size={12} /> {t('ai_panel.context')}</div>
|
|
<div className="italic truncate opacity-80">"{selectedText.substring(0, 60)}..."</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-theme-bg transition-colors duration-300">
|
|
{chatHistory.length === 0 && (
|
|
<div className="text-center text-theme-muted mt-10">
|
|
<Bot size={48} className="mx-auto mb-2 opacity-50" />
|
|
<p className="text-sm">{t('ai_panel.greeting')}</p>
|
|
{isLimitReached && (
|
|
<div className="mt-4 p-4 bg-red-50 border border-red-100 rounded-xl text-red-600 text-xs font-bold uppercase animate-pulse">
|
|
{t('ai_panel.limit_reached_upgrade')}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{chatHistory.map((msg) => (
|
|
<div key={msg.id} className={`flex flex-col ${msg.role === 'user' ? 'items-end' : 'items-start'}`}>
|
|
<div className={`max-w-[85%] rounded-2xl p-4 text-sm shadow-sm transition-colors duration-300 ${msg.role === 'user' ? 'bg-indigo-600 text-white rounded-br-none' : 'bg-theme-panel text-theme-text border border-theme-border rounded-bl-none'}`}>
|
|
{msg.role === 'model' && msg.responseType === 'reflection' && (
|
|
<div className="flex items-center gap-1.5 text-[10px] font-black text-amber-600 mb-1.5 uppercase tracking-wide"><BrainCircuit size={12} /> {t('ai_panel.reflection')}</div>
|
|
)}
|
|
<div className="whitespace-pre-wrap leading-relaxed">{msg.text}</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
{isGenerating && (
|
|
<div className="flex justify-start">
|
|
<div className="bg-theme-panel p-3 rounded-2xl rounded-bl-none shadow-sm border border-theme-border flex items-center gap-2 text-xs text-theme-muted transition-colors duration-300">
|
|
<RefreshCw size={14} className="animate-spin" /> {t('ai_panel.ai_working')}
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div ref={messagesEndRef} />
|
|
</div>
|
|
|
|
<div className="p-4 bg-theme-panel border-t border-theme-border transition-colors duration-300">
|
|
<form onSubmit={handleSubmit} className="relative">
|
|
<input
|
|
type="text"
|
|
value={input}
|
|
onChange={(e) => setInput(e.target.value)}
|
|
placeholder={isLimitReached ? t('ai_panel.limit_reached') : t('ai_panel.your_message')}
|
|
className="w-full pl-4 pr-12 py-3 bg-theme-bg text-theme-text border border-theme-border rounded-2xl text-sm focus:outline-none focus:border-indigo-500 transition-all disabled:opacity-50"
|
|
disabled={isGenerating || isLimitReached}
|
|
/>
|
|
<button
|
|
type="submit"
|
|
disabled={!input.trim() || isGenerating || isLimitReached}
|
|
className="absolute right-1.5 top-1.5 p-2 bg-indigo-600 text-white rounded-xl hover:bg-indigo-700 disabled:opacity-50 transition-colors shadow-md"
|
|
>
|
|
<Send size={18} />
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AIPanel;
|