aethex-forge/client/components/ai/ChatInput.tsx
sirpiglr 834c4bd56e Add AI chat assistant and backend API for AI interactions
Introduces new API endpoints for AI chat and title generation, integrates an AI chat component into the layout, and updates client-side services to communicate with the new backend AI endpoints.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 9203795e-937a-4306-b81d-b4d5c78c240e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 64961019-b4a5-48d8-97fc-c4980d29f3c4
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7c94b7a0-29c7-4f2e-94ef-44b2153872b7/9203795e-937a-4306-b81d-b4d5c78c240e/fhRML7y
Replit-Helium-Checkpoint-Created: true
2025-12-06 03:58:12 +00:00

86 lines
2.5 KiB
TypeScript

import React, { useState, useRef, useEffect } from 'react';
import type { Persona } from '@/lib/ai/types';
import { SendIcon } from './Icons';
import { Button } from '@/components/ui/button';
interface ChatInputProps {
onSendMessage: (message: string) => void;
isLoading: boolean;
persona: Persona;
isLocked?: boolean;
onUnlock?: () => void;
}
export const ChatInput: React.FC<ChatInputProps> = ({
onSendMessage,
isLoading,
persona,
isLocked,
onUnlock
}) => {
const [input, setInput] = useState('');
const textareaRef = useRef<HTMLTextAreaElement>(null);
const handleSubmit = (e?: React.FormEvent) => {
e?.preventDefault();
if (input.trim() && !isLoading && !isLocked) {
onSendMessage(input.trim());
setInput('');
}
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSubmit();
}
};
useEffect(() => {
if (textareaRef.current) {
textareaRef.current.style.height = 'auto';
textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 150)}px`;
}
}, [input]);
if (isLocked) {
return (
<div className="flex flex-col items-center justify-center p-4 bg-card/50 rounded-xl border border-border">
<p className="text-muted-foreground text-sm mb-3">
Upgrade to access {persona.name}
</p>
<Button
onClick={onUnlock}
className={persona.theme.button}
>
Unlock {persona.requiredTier} Tier
</Button>
</div>
);
}
return (
<form onSubmit={handleSubmit} className="relative">
<div className="flex items-end gap-2 bg-card/80 backdrop-blur-sm rounded-xl border border-border p-2 focus-within:border-primary/50 transition-colors">
<textarea
ref={textareaRef}
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={`Ask ${persona.name}...`}
disabled={isLoading}
rows={1}
className="flex-1 bg-transparent border-none outline-none resize-none text-foreground placeholder-muted-foreground text-sm md:text-base min-h-[40px] max-h-[150px] py-2 px-2"
/>
<Button
type="submit"
disabled={!input.trim() || isLoading}
size="icon"
className={`flex-shrink-0 ${persona.theme.button} disabled:opacity-50`}
>
<SendIcon className="w-4 h-4" />
</Button>
</div>
</form>
);
};