Spaces:
Runtime error
Runtime error
| import React, { useEffect, useRef, useState } from 'react' | |
| import { api } from '../api.js' | |
| const SUGGESTIONS = [ | |
| "What's the termination notice period?", | |
| "Who bears liability for data breaches?", | |
| "When does this contract expire?", | |
| "What are the payment terms?", | |
| "Is there an auto-renewal clause?", | |
| ] | |
| export default function ContractChat({ contractId, onSelectClause }) { | |
| const [messages, setMessages] = useState([ | |
| { | |
| role: 'bot', | |
| text: "Ask me anything about this contract — I'll search the clauses and cite my sources.", | |
| citations: [], | |
| }, | |
| ]) | |
| const [input, setInput] = useState('') | |
| const [loading, setLoading] = useState(false) | |
| const bottomRef = useRef(null) | |
| useEffect(() => { | |
| bottomRef.current?.scrollIntoView({ behavior: 'smooth' }) | |
| }, [messages, loading]) | |
| async function send(question) { | |
| const q = (question || input).trim() | |
| if (!q || loading) return | |
| setInput('') | |
| setMessages((m) => [...m, { role: 'user', text: q, citations: [] }]) | |
| setLoading(true) | |
| try { | |
| const res = await api(`/api/contracts/${contractId}/chat`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ question: q }), | |
| }) | |
| const data = await res.json() | |
| setMessages((m) => [ | |
| ...m, | |
| { role: 'bot', text: data.answer || 'No answer returned.', citations: data.citations || [] }, | |
| ]) | |
| } catch { | |
| setMessages((m) => [ | |
| ...m, | |
| { role: 'bot', text: 'Error contacting the server. Please try again.', citations: [] }, | |
| ]) | |
| } finally { | |
| setLoading(false) | |
| } | |
| } | |
| const showSuggestions = messages.length === 1 | |
| return ( | |
| <div className="chat-wrap"> | |
| <div className="chat-messages"> | |
| {messages.map((m, i) => ( | |
| <div key={i} className={`chat-msg ${m.role}`}> | |
| <div className="chat-bubble"> | |
| {m.text.split('\n\n').map((para, j) => ( | |
| <p key={j} className="chat-para">{para}</p> | |
| ))} | |
| </div> | |
| {m.citations.length > 0 && ( | |
| <div className="chat-citations"> | |
| <span className="cite-label">Sources:</span> | |
| {m.citations.map((c, j) => ( | |
| <button | |
| key={j} | |
| className="citation-chip" | |
| title={c.snippet} | |
| onClick={() => onSelectClause(c.clause_id)} | |
| > | |
| § {c.heading} | |
| </button> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| ))} | |
| {showSuggestions && ( | |
| <div className="chat-suggestions"> | |
| {SUGGESTIONS.map((s, i) => ( | |
| <button key={i} className="suggestion-chip" onClick={() => send(s)}> | |
| {s} | |
| </button> | |
| ))} | |
| </div> | |
| )} | |
| {loading && ( | |
| <div className="chat-msg bot"> | |
| <div className="chat-bubble chat-thinking"> | |
| <span className="dot-pulse" /> | |
| </div> | |
| </div> | |
| )} | |
| <div ref={bottomRef} /> | |
| </div> | |
| <div className="chat-input-row"> | |
| <input | |
| className="chat-input" | |
| placeholder="Ask about termination, liability, payment…" | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| onKeyDown={(e) => e.key === 'Enter' && send()} | |
| disabled={loading} | |
| /> | |
| <button | |
| className="chat-send" | |
| onClick={() => send()} | |
| disabled={loading || !input.trim()} | |
| > | |
| ↑ | |
| </button> | |
| </div> | |
| </div> | |
| ) | |
| } | |