moxieTalking/src/app/api/chat/route.ts
Z User e4408a63e6 feat: add Echo voice assistant web UI
- Conversation sidebar with create/delete/history
- Chat area with streaming LLM responses (z-ai-web-dev-sdk)
- Voice input via Web Speech API with recording indicator
- Browser TTS auto-speak for assistant responses
- Settings panel (voice, TTS, sidebar toggle)
- Prisma schema: Conversation + Message models
- API routes: /api/chat/stream, /api/conversations, /api/messages
- Zustand store for state management
- Web Speech API type declarations
2026-03-31 00:42:10 +00:00

85 lines
2.7 KiB
TypeScript

import { NextRequest } from "next/server";
import ZAI from "z-ai-web-dev-sdk";
const SYSTEM_PROMPT = `You are Echo, a concise, helpful voice assistant. Follow these rules strictly:
1. **Verbal response**: Reply in ≤ 2 short sentences so it sounds natural when spoken aloud. Be direct and conversational.
2. **Local commands** (optional): If the user's request can be fulfilled by a local OS action, include a single JSON block at the very end of your response using this exact format:
\`\`\`json
{"action": "<command_name>", "params": {"key": "value"}}
\`\`\`
3. Do NOT include the JSON block in your spoken text.
4. Never use markdown formatting, bullet points, or headers in the spoken text.
5. If the user asks you to do something you cannot do, be honest about it briefly.`;
// Store conversation history per session
const sessionHistories = new Map<string, Array<{ role: string; content: string }>>();
export async function POST(request: NextRequest) {
try {
const { message, conversationId } = await request.json();
if (!message) {
return new Response(JSON.stringify({ error: "Message is required" }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
// Get or create session history
const history = sessionHistories.get(conversationId) || [];
history.push({ role: "user", content: message });
// Keep last 20 messages for context
const recentHistory = history.slice(-20);
const zai = await ZAI.create();
const completion = await zai.chat.completions.create({
messages: [
{ role: "system", content: SYSTEM_PROMPT },
...recentHistory,
],
temperature: 0.7,
max_tokens: 300,
});
const assistantMessage =
completion.choices[0]?.message?.content || "I'm sorry, I couldn't process that.";
// Clean any thinking tags
const cleanedMessage = assistantMessage
.replace(/<think[^>]*>.*?<\/think\s*>/gs, "")
.replace(/```json.*?```/gs, "")
.trim();
// Store in history
history.push({ role: "assistant", content: cleanedMessage });
sessionHistories.set(conversationId, history);
return new Response(
JSON.stringify({
message: cleanedMessage,
rawMessage: assistantMessage,
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
} catch (error) {
console.error("Chat API error:", error);
return new Response(
JSON.stringify({
message: "Sorry, I had trouble thinking about that. Please try again.",
error: true,
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
}