Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,27 @@
"prepare": "husky"
},
"dependencies": {
"@ai-sdk/google-vertex": "^2.2.27",
"@apollo/client": "^3.13.8",
"@ai-sdk/google-vertex": "^3.0.5",
"@ai-sdk/react": "^2.0.9",
"@apollo/client": "^3.13.9",
"@faustwp/blocks": "^6.1.3",
"@faustwp/cli": "^3.2.4",
"@faustwp/cli": "^3.2.5",
"@faustwp/core": "^3.2.4",
"@headlessui/react": "^2.2.6",
"@headlessui/react": "^2.2.7",
"@jsdevtools/rehype-url-inspector": "^2.0.2",
"@next/third-parties": "^15.4.4",
"@next/third-parties": "^15.4.6",
"@octokit/core": "^7.0.3",
"@shikijs/transformers": "^3.8.1",
"@shikijs/transformers": "^3.9.2",
"@sindresorhus/slugify": "^2.2.1",
"@wpengine/atlas-next": "^3.0.0",
"ai": "^4.3.19",
"ai": "^5.0.9",
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0",
"feed": "^5.1.0",
"graphql": "^16.11.0",
"http-status-codes": "^2.3.0",
"lodash.debounce": "^4.0.8",
"next": "^15.4.4",
"next": "^15.4.6",
"next-mdx-remote-client": "^2.1.3",
"next-sitemap": "^4.2.3",
"react": "^19.1.1",
Expand All @@ -57,22 +58,22 @@
"remark-parse": "^11.0.0",
"remark-smartypants": "^3.0.2",
"remark-stringify": "^11.0.0",
"shiki": "^3.8.1",
"shiki": "^3.9.2",
"strip-markdown": "^6.0.0",
"unified": "^11.0.5",
"vfile-matter": "^5.0.1",
"zod": "^4.0.13"
"zod": "^4.0.17"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.11",
"@tailwindcss/typography": "^0.5.16",
"concurrently": "^9.2.0",
"eslint": "^9.32.0",
"eslint": "^9.33.0",
"eslint-config-neon": "^0.2.7",
"eslint-plugin-mdx": "^3.6.2",
"eslint-plugin-unicorn": "^59.0.1",
"husky": "^9.1.7",
"lint-staged": "^16.1.2",
"lint-staged": "^16.1.5",
"next-secure-headers": "^2.2.0",
"postcss": "^8.5.6",
"postcss-nesting": "^13.0.2",
Expand All @@ -84,7 +85,7 @@
"node": "^22",
"npm": "use-pnpm"
},
"packageManager": "[email protected]-0+sha512.2cd47a0cbf5f1d1de7693a88307a0ede5be94e0d3b34853d800ee775efbea0650cb562b77605ec80bc8d925f5cd27c4dfe8bb04d3a0b76090784c664450d32d6",
"packageManager": "[email protected]+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748",
"lint-staged": {
"*": "pnpm format",
"*.{js,jsx}": "pnpm lint"
Expand Down
920 changes: 472 additions & 448 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions scripts/smart-search.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async function main() {
try {
const pages = await collectPages();

console.log("Docs Pages collected for indexing:", pages.length);
console.info("Docs Pages collected for indexing:", pages.length);

await deleteOldDocs();

Expand Down Expand Up @@ -146,7 +146,7 @@ async function deleteOldDocs() {
const existingIndexedDocuments = new Set(existingDocs.map((doc) => doc.id));

if (existingIndexedDocuments?.size === 0) {
console.log("No documents to delete.");
console.info("No documents to delete.");
return;
}

Expand All @@ -173,7 +173,7 @@ async function deleteOldDocs() {
}
}

console.log(`Deleted ${results.length} documents successfully.`);
console.info(`Deleted ${results.length} documents successfully.`);
} catch (error) {
console.error("Error during deletion process:", error);
}
Expand Down Expand Up @@ -206,7 +206,7 @@ async function sendPagesToEndpoint(pages) {
try {
await graphql({ query: bulkIndexMutation, variables });

console.log(`Indexed ${documents.length} documents successfully.`);
console.info(`Indexed ${documents.length} documents successfully.`);
} catch (error) {
console.error("Error during bulk indexing:", error);
}
Expand All @@ -233,7 +233,7 @@ async function setSearchConfig() {

try {
const response = await graphql({ query: searchConfigMutation, variables });
console.log(
console.info(
"Search configuration updated successfully.",
JSON.stringify(response.data.config.semanticSearch, undefined, 2),
);
Expand Down
14 changes: 6 additions & 8 deletions src/app/api/chat/route.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { env } from "node:process";
import { createVertex } from "@ai-sdk/google-vertex";
import { streamText, convertToCoreMessages } from "ai";
import { streamText, convertToModelMessages } from "ai";
import { StatusCodes, ReasonPhrases } from "http-status-codes";
import { smartSearchTool } from "@/lib/rag.mjs";

Expand Down Expand Up @@ -68,12 +68,10 @@ export async function POST(req) {
});
}

const coreMessages = convertToCoreMessages(messages);

const response = await streamText({
model: vertex("gemini-2.5-flash"),
system: [systemPromptContent, smartSearchPrompt].join("\n"),
messages: coreMessages,
messages: convertToModelMessages(messages),
tools: {
smartSearchTool,
},
Expand All @@ -84,19 +82,19 @@ export async function POST(req) {
});
},
onToolCall: async (toolCall) => {
console.log("Tool call initiated:", toolCall);
console.info("Tool call initiated:", toolCall);
},
onStepFinish: async (result) => {
if (result.usage) {
console.log(
`[Token Usage] Prompt tokens: ${result.usage.promptTokens}, Completion tokens: ${result.usage.completionTokens}, Total tokens: ${result.usage.totalTokens}`,
console.info(
`[Token Usage] Prompt tokens: ${result.usage.inputTokens}, Completion tokens: ${result.usage.outputTokens}, Total tokens: ${result.usage.totalTokens}`,
);
}
},
maxSteps: 5,
});

return response.toDataStreamResponse();
return response.toUIMessageStreamResponse();
} catch (error) {
console.error("Error in chat API:", error);
return new Response(ReasonPhrases.INTERNAL_SERVER_ERROR, {
Expand Down
48 changes: 24 additions & 24 deletions src/components/chat/chat-dialog.jsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
import { useChat } from "ai/react";
import { useEffect } from "react";
import { useChat } from "@ai-sdk/react";
import { lastAssistantMessageIsCompleteWithToolCalls } from "ai";
import { useState } from "react";
import { HiXCircle } from "react-icons/hi2";
import { useChatDialog } from "./state";
import Chat from "@/components/chat/chat";
import "./chat.css";

export default function ChatDialog() {
const { dialog } = useChatDialog();
const {
messages,
input,
handleInputChange,
handleSubmit,
setMessages,
status,
} = useChat();
const { messages, sendMessage, status } = useChat({
onError: (error) => {
console.error("Error sending message:", error);
},
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
messages: [
{
role: "assistant",
parts: [
{
type: "text",
text: "Hey there! I'm an AI driven chat assistant here to help you with Faust.js! I'm trained on the documentation and can help you with coding tasks, learning, and more. What can I assist you with today?",
},
],
id: "welcome-intro",
},
],
});

useEffect(() => {
if (messages.length === 0) {
setMessages([
{
role: "assistant",
content:
"Hey there! I'm an AI driven chat assistant here to help you with Faust.js! I'm trained on the documentation and can help you with coding tasks, learning, and more. What can I assist you with today?",
id: "welcome-intro",
},
]);
}
}, [messages, setMessages]);
const [input, setInput] = useState("");

return (
<dialog
Expand All @@ -54,8 +54,8 @@ export default function ChatDialog() {
<section>
<Chat
input={input}
handleInputChange={handleInputChange}
handleMessageSubmit={handleSubmit}
setInput={setInput}
sendMessage={sendMessage}
messages={messages}
status={status}
/>
Expand Down
6 changes: 4 additions & 2 deletions src/components/chat/chat-input.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HiOutlinePaperAirplane, HiOutlineArrowPath } from "react-icons/hi2";

export default function Input({ input, handleInputChange, status }) {
export default function Input({ input, setInput, status }) {
const isReady = status === "ready";
const isSubmitted = status === "submitted";

Expand All @@ -11,7 +11,9 @@ export default function Input({ input, handleInputChange, status }) {
type="text"
wrap="soft"
value={input}
onChange={handleInputChange}
onChange={(event) => {
setInput(event.target.value);
}}
autoFocus
placeholder="Ask about Faust..."
className="no-scrollbar text-md w-full max-w-full rounded-xl bg-gray-700 p-2 text-wrap text-gray-200 placeholder-gray-400 shadow-lg transition-colors focus:ring-2 focus:ring-teal-500 focus:outline-none"
Expand Down
16 changes: 8 additions & 8 deletions src/components/chat/chat.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { sendChatMessageEvent } from "@/lib/analytics.mjs";

export default function Chat({
input,
handleInputChange,
handleMessageSubmit,
setInput,
sendMessage,
status,
messages,
}) {
Expand All @@ -15,19 +15,19 @@ export default function Chat({
<form
id="chat-form"
onSubmit={(event) => {
event.preventDefault();

sendChatMessageEvent({
message: input,
});

return handleMessageSubmit(event);
sendMessage({ text: input });

setInput("");
}}
className="absolute bottom-0 left-0 w-[calc(100%-theme(spacing.[1.5]))] bg-gradient-to-b from-transparent via-gray-800 to-gray-800 p-4 md:p-6"
>
<ChatInput
input={input}
handleInputChange={handleInputChange}
status={status}
/>
<ChatInput input={input} setInput={setInput} status={status} />
</form>
</div>
);
Expand Down
29 changes: 19 additions & 10 deletions src/components/chat/messages.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,14 @@ export default function Messages({ messages, className }) {
role="log"
>
{messages.map((message) => {
const isAssistant = message.role === "assistant";
const isLoading = message.content === "";
const isAssistant = message.role !== "user";
const isLoading =
message.parts?.length <= 2 &&
message.parts[0].type === "step-start" &&
!(
message.parts?.length && Object.hasOwn(message.parts.at(-1), "text")
);

return (
<div
key={message.id}
Expand All @@ -39,14 +45,17 @@ export default function Messages({ messages, className }) {
<div className="animate-think h-2 w-2 rounded-full bg-gray-200" />
</div>
) : (
<Markdown
remarkPlugins={[remarkGfm]}
components={{
a: ChatLink,
}}
>
{message.content}
</Markdown>
message.parts?.map((part, index) => (
<Markdown
key={index}
remarkPlugins={[remarkGfm]}
components={{
a: ChatLink,
}}
>
{part.text}
</Markdown>
))
)}
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions src/lib/rag.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import { normalizeSmartSearchResponse } from "@/lib/smart-search.mjs";
export const smartSearchTool = tool({
description:
"Search for information about Faust using WP Engine Smart Search. Use this to answer questions about Faust, its features, capabilities, and more when the information is not already known.",
parameters: z.object({
inputSchema: z.object({
query: z
.string()
.describe(
"The search query to find relevant Faust information based on the user's question.",
),
}),
execute: async ({ query }) => {
console.log(`[Tool Execution] Searching with query: "${query}"`);
console.info(`[Tool Execution] Searching with query: "${query}"`);
try {
const context = await getContext(query);

Expand Down