Fix AI chat performance and UX
- Single JOIN query instead of N+1 selects for sessions - Auto-create session when sending without one - Send button enabled when typing Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
881888ef10
commit
9e506279a7
2 changed files with 50 additions and 26 deletions
|
|
@ -70,25 +70,53 @@ export default function AIChatPage() {
|
||||||
const currentSession = sessions.find(s => s.id === currentSessionId);
|
const currentSession = sessions.find(s => s.id === currentSessionId);
|
||||||
|
|
||||||
const handleSend = async () => {
|
const handleSend = async () => {
|
||||||
if (!input.trim() || loading || !currentSession) return;
|
if (!input.trim() || loading) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const userContent = input.trim();
|
const userContent = input.trim();
|
||||||
setInput("");
|
setInput("");
|
||||||
|
|
||||||
|
// Auto-create session if none selected
|
||||||
|
let sessionId = currentSessionId;
|
||||||
|
if (!sessionId && childId) {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/chat", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ childId, title: userContent.slice(0, 30) }),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.session) {
|
||||||
|
sessionId = data.session.id;
|
||||||
|
const newSession = { ...data.session, messages: [] };
|
||||||
|
setSessions([newSession, ...sessions]);
|
||||||
|
setCurrentSessionId(sessionId);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to create session:", err);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sessionId) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Save user message
|
// Save user message
|
||||||
await fetch("/api/chat", {
|
await fetch("/api/chat", {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ sessionId: currentSessionId, role: "user", content: userContent }),
|
body: JSON.stringify({ sessionId, role: "user", content: userContent }),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get AI response
|
// Get AI response
|
||||||
const aiRes = await fetch("/api/ai", {
|
const aiRes = await fetch("/api/ai", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ messages: currentSession.messages.concat({ id: "", role: "user", content: userContent, createdAt: "" }) }),
|
body: JSON.stringify({ messages: [{ role: "user", content: userContent }] }),
|
||||||
});
|
});
|
||||||
const aiData = await aiRes.json();
|
const aiData = await aiRes.json();
|
||||||
|
|
||||||
|
|
@ -97,7 +125,7 @@ export default function AIChatPage() {
|
||||||
await fetch("/api/chat", {
|
await fetch("/api/chat", {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ sessionId: currentSessionId, role: "assistant", content: aiData.reply }),
|
body: JSON.stringify({ sessionId, role: "assistant", content: aiData.reply }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,7 +201,7 @@ export default function AIChatPage() {
|
||||||
className="flex-1 p-3 border rounded-lg"
|
className="flex-1 p-3 border rounded-lg"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
/>
|
/>
|
||||||
<button onClick={handleSend} disabled={loading || !currentSession} className={`px-4 py-2 text-white rounded-lg ${loading || !currentSession ? "bg-gray-300 cursor-not-allowed" : "bg-rose-400"}`}>
|
<button onClick={handleSend} disabled={loading || !input.trim()} className={`px-4 py-2 text-white rounded-lg ${loading || !input.trim() ? "bg-gray-300 cursor-not-allowed" : "bg-rose-400"}`}>
|
||||||
{loading ? "..." : "Send"}
|
{loading ? "..." : "Send"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,28 +7,24 @@ export async function GET(request: Request) {
|
||||||
const childId = searchParams.get("childId") || "default";
|
const childId = searchParams.get("childId") || "default";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch sessions
|
// Single query with JOIN for better performance
|
||||||
const sessions = await sql`
|
const sessions = await sql.unsafe(`
|
||||||
SELECT id, title, created_at as "createdAt", updated_at as "updatedAt"
|
SELECT
|
||||||
FROM chat_sessions
|
cs.id, cs.title, cs.created_at as "createdAt", cs.updated_at as "updatedAt",
|
||||||
WHERE child_id = ${childId}
|
COALESCE(
|
||||||
ORDER BY updated_at DESC
|
json_agg(
|
||||||
`;
|
json_build_object('id', cm.id, 'role', cm.role, 'content', cm.content, 'createdAt', cm.created_at ORDER BY cm.created_at)
|
||||||
|
) FILTER (WHERE cm.id IS NOT NULL),
|
||||||
|
'[]'::json
|
||||||
|
) as messages
|
||||||
|
FROM chat_sessions cs
|
||||||
|
LEFT JOIN chat_messages cm ON cm.session_id = cs.id
|
||||||
|
WHERE cs.child_id = $1
|
||||||
|
GROUP BY cs.id
|
||||||
|
ORDER BY cs.updated_at DESC
|
||||||
|
`, [childId]);
|
||||||
|
|
||||||
// Fetch messages for each session
|
return NextResponse.json({ sessions: sessions || [] });
|
||||||
const sessionsWithMessages = await Promise.all(
|
|
||||||
(sessions || []).map(async (session: any) => {
|
|
||||||
const messages = await sql`
|
|
||||||
SELECT id, role, content, created_at as "createdAt"
|
|
||||||
FROM chat_messages
|
|
||||||
WHERE session_id = ${session.id}
|
|
||||||
ORDER BY created_at
|
|
||||||
`;
|
|
||||||
return { ...session, messages: messages || [] };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return NextResponse.json({ sessions: sessionsWithMessages || [] });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return NextResponse.json({ error: String(error) }, { status: 500 });
|
return NextResponse.json({ error: String(error) }, { status: 500 });
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue