diff --git a/intelligence/ts/examples/nextjs-web-chats/app/api/chat/route.ts b/intelligence/ts/examples/nextjs-web-chats/app/api/chat/route.ts index 5f1ef70ad10d..26a3355b777c 100644 --- a/intelligence/ts/examples/nextjs-web-chats/app/api/chat/route.ts +++ b/intelligence/ts/examples/nextjs-web-chats/app/api/chat/route.ts @@ -1,8 +1,25 @@ -import { chatWithHistory } from '@/lib/chat'; +import { chatWithHistory, chatWithMessages } from '@/lib/chat'; +import type { Message } from '@flwr/flwr'; export async function POST(req: Request) { try { - const { question } = await req.json(); + const { question, messages } = (await req.json()) as { + question?: string; + messages?: Message[]; + }; + + if (Array.isArray(messages) && messages.length > 0) { + const responseMessage = await chatWithMessages(messages); + return new Response( + JSON.stringify({ message: responseMessage.content, role: responseMessage.role }), + { status: 200 } + ); + } + + if (!question?.trim()) { + return new Response(JSON.stringify({ error: 'Missing question.' }), { status: 400 }); + } + const message = await chatWithHistory(question); return new Response(JSON.stringify({ message }), { status: 200 }); } catch (error) { diff --git a/intelligence/ts/examples/nextjs-web-chats/app/client-side-chat/page.tsx b/intelligence/ts/examples/nextjs-web-chats/app/client-side-chat/page.tsx index 200eefc1e0ca..27345199f1ec 100644 --- a/intelligence/ts/examples/nextjs-web-chats/app/client-side-chat/page.tsx +++ b/intelligence/ts/examples/nextjs-web-chats/app/client-side-chat/page.tsx @@ -1,15 +1,15 @@ 'use client'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; -import { FlowerIntelligence, ChatResponseResult, ChatOptions, Message } from '@flwr/flwr'; -const fi: FlowerIntelligence = FlowerIntelligence.instance; +type ChatMessageRole = 'system' | 'user' | 'assistant'; -const history: Message[] = [ - { role: 'system', content: 'You are a friendly assistant that loves using emojis.' }, -]; +interface ChatMessage { + role: ChatMessageRole; + content: string; +} interface ChatEntry { role: 'user' | 'bot'; @@ -17,57 +17,59 @@ interface ChatEntry { } export default function ClientSideChatPage() { - // Initialize local state using the current global history (excluding the system message) - const [chatLog, setChatLog] = useState( - history - .filter((msg) => msg.role !== 'system') - .map((msg) => ({ - role: msg.role as 'user' | 'bot', - content: msg.content, - })) - ); + const [messages, setMessages] = useState([ + { role: 'system', content: 'You are a friendly assistant that loves using emojis.' }, + ]); const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); + const chatLog = useMemo( + () => + messages + .filter((msg) => msg.role !== 'system') + .map((msg) => ({ + role: msg.role === 'user' ? 'user' : 'bot', + content: msg.content, + })), + [messages] + ); + const sendQuestion = async () => { - if (!input.trim()) return; + const trimmed = input.trim(); + if (!trimmed) return; setLoading(true); - setChatLog((prev) => [...prev, { role: 'user', content: input }]); + setInput(''); + + const nextMessages: ChatMessage[] = [...messages, { role: 'user', content: trimmed }]; + setMessages(nextMessages); - // Append user's question to the global history. - history.push({ role: 'user', content: input }); try { - // Call the FlowerIntelligence client directly with updated history. - const response: ChatResponseResult = await fi.chat(input, { - messages: history, - } as ChatOptions); - if (response.ok) { - history.push(response.message); + const res = await fetch('/api/chat', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ messages: nextMessages }), + }); + const data = (await res.json()) as { + message?: string; + role?: ChatMessageRole; + error?: string; + }; + if (res.ok && data.message) { + const message = data.message; + setMessages((prev) => [...prev, { role: data.role ?? 'assistant', content: message }]); } else { - history.push({ role: 'bot', content: 'Failed to get a valid response.' }); + setMessages((prev) => [ + ...prev, + { role: 'assistant', content: data.error || 'Failed to get a valid response.' }, + ]); } } catch { - setChatLog([ - ...history - .filter((msg) => msg.role !== 'system') - .map((msg) => ({ - role: msg.role as 'user' | 'bot', - content: msg.content, - })), - ]); - } finally { - // Update local chat log to reflect the global history (filtering out the system message). - setChatLog([ - ...history - .filter((msg) => msg.role !== 'system') - .map((msg) => ({ - role: msg.role as 'user' | 'bot', - content: msg.content, - })), + setMessages((prev) => [ + ...prev, + { role: 'assistant', content: 'Network error, please try again.' }, ]); - setInput(''); - setLoading(false); } + setLoading(false); }; const handleKeyPress = (e: React.KeyboardEvent) => { diff --git a/intelligence/ts/examples/nextjs-web-chats/lib/chat.ts b/intelligence/ts/examples/nextjs-web-chats/lib/chat.ts index de1cfff4a13d..9c06e884b829 100644 --- a/intelligence/ts/examples/nextjs-web-chats/lib/chat.ts +++ b/intelligence/ts/examples/nextjs-web-chats/lib/chat.ts @@ -7,6 +7,26 @@ export const history: Message[] = [ { role: 'system', content: 'You are a friendly assistant that loves using emojis.' }, ]; +export async function chatWithMessages(messages: Message[]): Promise { + try { + const response: ChatResponseResult = await fi.chat({ + messages, + }); + if (!response || (response.ok && !response.message)) { + throw new Error('Invalid response structure from the chat service.'); + } + if (!response.ok) { + console.error(response); + throw new Error('Failed to get a valid response.'); + } + + return response.message; + } catch (error) { + console.error('Error in chatWithMessages:', error); + throw new Error('Failed to get a valid response from the chat service.'); + } +} + export async function chatWithHistory(question: string): Promise { try { history.push({ role: 'user', content: question }); diff --git a/intelligence/ts/examples/nextjs-web-chats/next.config.ts b/intelligence/ts/examples/nextjs-web-chats/next.config.ts index 5e891cf00c22..411b6e897bba 100644 --- a/intelligence/ts/examples/nextjs-web-chats/next.config.ts +++ b/intelligence/ts/examples/nextjs-web-chats/next.config.ts @@ -1,7 +1,10 @@ import type { NextConfig } from 'next'; const nextConfig: NextConfig = { - /* config options here */ + turbopack: { + root: process.cwd(), + }, + serverExternalPackages: ['@flwr/flwr', '@huggingface/transformers', 'onnxruntime-node'], }; export default nextConfig; diff --git a/intelligence/ts/examples/nextjs-web-chats/package.json b/intelligence/ts/examples/nextjs-web-chats/package.json index 56ad30598f18..58edc8163678 100644 --- a/intelligence/ts/examples/nextjs-web-chats/package.json +++ b/intelligence/ts/examples/nextjs-web-chats/package.json @@ -11,7 +11,7 @@ "dependencies": { "@flwr/flwr": "^0.2.5", "@tailwindcss/typography": "^0.5.19", - "next": "15.4.11", + "next": "16.1.6", "react": "^19.2.4", "react-dom": "^19.2.4", "react-markdown": "^10.1.0", @@ -23,6 +23,7 @@ "@types/node": "^22.19.7", "@types/react": "^19.2.10", "@types/react-dom": "^19.2.3", + "baseline-browser-mapping": "^2.9.19", "eslint": "^9.39.2", "eslint-config-next": "15.2.4", "tailwindcss": "^4.1.18", diff --git a/intelligence/ts/examples/nextjs-web-chats/tsconfig.json b/intelligence/ts/examples/nextjs-web-chats/tsconfig.json index d8b93235f205..705f5ce5e368 100644 --- a/intelligence/ts/examples/nextjs-web-chats/tsconfig.json +++ b/intelligence/ts/examples/nextjs-web-chats/tsconfig.json @@ -11,7 +11,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve", + "jsx": "react-jsx", "incremental": true, "plugins": [ { @@ -22,6 +22,12 @@ "@/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], "exclude": ["node_modules"] }