new changes
This commit is contained in:
parent
b980f7a052
commit
3e6202f6d3
24
src/App.tsx
24
src/App.tsx
|
@ -6,6 +6,8 @@ import { ChatInterface } from './components/ChatInterface';
|
|||
import { TicketConfirmation } from './components/TicketConfirmation';
|
||||
import { apiService } from './services/apiService';
|
||||
import { Ticket } from './types';
|
||||
import { FilePlus } from 'lucide-react'; // Add this import at the top
|
||||
|
||||
|
||||
function App() {
|
||||
const [activeTab, setActiveTab] = useState<'new' | 'history' | 'chat'>('new');
|
||||
|
@ -114,15 +116,25 @@ function App() {
|
|||
|
||||
{/* Main Content */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
{activeTab === 'new' && (
|
||||
<div>
|
||||
<div className="mb-8">
|
||||
<h2 className="text-2xl font-bold text-white mb-2">Submit New Ticket</h2>
|
||||
<p className="text-gray-400">Use voice or text to describe your IT issue</p>
|
||||
{activeTab === 'new' && (
|
||||
<div className="flex items-center justify-center min-h-[80vh] px-4">
|
||||
<div className="bg-gray-800 border border-gray-700 rounded-2xl shadow-2xl p-8 max-w-xl w-full text-center transition-all duration-300 hover:shadow-blue-700/30">
|
||||
|
||||
{/* Header with icon */}
|
||||
<div className="mb-6 flex flex-col items-center">
|
||||
<div className="bg-gradient-to-r from-blue-600 to-purple-600 p-3 rounded-full shadow-lg mb-4">
|
||||
<FilePlus className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h2 className="text-3xl font-extrabold text-white mb-2">Submit a New Ticket</h2>
|
||||
<p className="text-gray-400 text-sm">Describe your IT issue using voice or text — we’ll take it from here!</p>
|
||||
</div>
|
||||
|
||||
{/* Ticket form */}
|
||||
<TicketForm onTicketSubmit={handleTicketSubmit} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{activeTab === 'history' && (
|
||||
<div>
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, { useState, useRef, useEffect } from 'react';
|
|||
import { Send, Bot, User, Loader, MessageSquare, UserPlus } from 'lucide-react';
|
||||
import { ChatMessage } from '../types';
|
||||
import { ChatService } from '../services/chatService';
|
||||
import { Mic, MicOff } from 'lucide-react';
|
||||
|
||||
interface ChatInterfaceProps {
|
||||
onCreateTicket?: (conversation: string) => void;
|
||||
|
@ -23,22 +24,61 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({ onCreateTicket })
|
|||
const [isCreatingTicket, setIsCreatingTicket] = useState(false);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const [chatService] = useState(() => new ChatService());
|
||||
const [isListening, setIsListening] = useState(false);
|
||||
const recognitionRef = useRef<SpeechRecognition | null>(null);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [messages]);
|
||||
const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;
|
||||
|
||||
const handleSend = async () => {
|
||||
if (!input.trim() || isLoading) return;
|
||||
if (SpeechRecognition) {
|
||||
const recognition = new SpeechRecognition();
|
||||
recognition.lang = 'en-US';
|
||||
recognition.interimResults = false;
|
||||
recognition.maxAlternatives = 1;
|
||||
|
||||
recognition.onstart = () => setIsListening(true);
|
||||
recognition.onend = () => setIsListening(false);
|
||||
recognition.onerror = (e: any) => {
|
||||
console.error('Speech recognition error', e);
|
||||
setIsListening(false);
|
||||
};
|
||||
recognition.onresult = (event: SpeechRecognitionEvent) => {
|
||||
const transcript = event.results[0][0].transcript;
|
||||
setInput(transcript);
|
||||
handleSend(transcript);
|
||||
};
|
||||
|
||||
recognitionRef.current = recognition;
|
||||
} else {
|
||||
console.warn('SpeechRecognition is not supported in this browser.');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const speak = (text: string) => {
|
||||
if ('speechSynthesis' in window) {
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.lang = 'en-US';
|
||||
utterance.pitch = 1;
|
||||
utterance.rate = 1;
|
||||
utterance.volume = 1;
|
||||
window.speechSynthesis.speak(utterance);
|
||||
} else {
|
||||
console.warn('Speech synthesis not supported in this browser.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSend = async (messageOverride?: string) => {
|
||||
const messageToSend = messageOverride ?? input;
|
||||
if (!messageToSend.trim() || isLoading) return;
|
||||
|
||||
const userMessage: ChatMessage = {
|
||||
id: Date.now().toString(),
|
||||
role: 'user',
|
||||
content: input,
|
||||
content: messageToSend,
|
||||
timestamp: new Date().toLocaleString()
|
||||
};
|
||||
|
||||
|
@ -57,12 +97,13 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({ onCreateTicket })
|
|||
};
|
||||
|
||||
setMessages(prev => [...prev, assistantMessage]);
|
||||
speak(response);
|
||||
} catch (error) {
|
||||
console.error('Chat error:', error);
|
||||
const errorMessage: ChatMessage = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
role: 'assistant',
|
||||
content: "I apologize, but I'm experiencing some technical difficulties. Please try again or create a support ticket if the issue persists.",
|
||||
content: "I apologize, but I'm experiencing some technical difficulties.",
|
||||
timestamp: new Date().toLocaleString()
|
||||
};
|
||||
setMessages(prev => [...prev, errorMessage]);
|
||||
|
@ -71,6 +112,7 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({ onCreateTicket })
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
|
@ -78,6 +120,17 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({ onCreateTicket })
|
|||
}
|
||||
};
|
||||
|
||||
const handleMicClick = () => {
|
||||
if (recognitionRef.current) {
|
||||
if (isListening) {
|
||||
recognitionRef.current.stop();
|
||||
} else {
|
||||
recognitionRef.current.start();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleCreateTicketFromChat = async () => {
|
||||
if (!ticketFormData.requester.trim() || !ticketFormData.email.trim()) {
|
||||
return;
|
||||
|
@ -260,13 +313,38 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({ onCreateTicket })
|
|||
rows={1}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSend}
|
||||
disabled={!input.trim() || isLoading}
|
||||
className="px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white rounded-lg transition-all duration-200 hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100"
|
||||
>
|
||||
<Send className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="flex gap-3">
|
||||
|
||||
|
||||
<button
|
||||
onClick={handleMicClick}
|
||||
className={`relative flex items-center justify-center px-4 py-3 rounded-lg transition-all duration-200
|
||||
${isListening ? 'bg-red-600 animate-pulse' : 'bg-gray-700 hover:bg-gray-600'}
|
||||
text-white disabled:opacity-50 disabled:cursor-not-allowed hover:scale-105`}
|
||||
disabled={isLoading}
|
||||
title={isListening ? "Stop Listening" : "Start Voice Input"}
|
||||
>
|
||||
{isListening ? (
|
||||
<MicOff className="w-5 h-5" />
|
||||
) : (
|
||||
<Mic className="w-5 h-5" />
|
||||
)}
|
||||
|
||||
{isListening && (
|
||||
<span className="absolute -bottom-6 text-xs text-red-400 animate-pulse">Listening...</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
onClick={() => handleSend()}
|
||||
disabled={!input.trim() || isLoading}
|
||||
className="px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white rounded-lg transition-all duration-200 hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<Send className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -68,13 +68,7 @@ export const TicketForm: React.FC<TicketFormProps> = ({ onTicketSubmit }) => {
|
|||
<div className="max-w-2xl mx-auto">
|
||||
<div className="bg-gray-900 rounded-xl p-6 shadow-2xl border border-gray-800">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-10 bg-blue-600 rounded-lg flex items-center justify-center">
|
||||
<FileText className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-white">Create New Ticket</h2>
|
||||
<p className="text-gray-400 text-sm">Submit your IT support request</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
|
@ -86,7 +80,7 @@ export const TicketForm: React.FC<TicketFormProps> = ({ onTicketSubmit }) => {
|
|||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2 text-left">
|
||||
<User className="w-4 h-4 inline mr-2" />
|
||||
Your Name
|
||||
</label>
|
||||
|
@ -102,10 +96,11 @@ export const TicketForm: React.FC<TicketFormProps> = ({ onTicketSubmit }) => {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<Mail className="w-4 h-4 inline mr-2" />
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2 text-left flex items-center gap-2">
|
||||
<Mail className="w-4 h-4 text-gray-400" />
|
||||
Email Address
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="email"
|
||||
value={formData.email}
|
||||
|
|
Loading…
Reference in a new issue