Skip to content

Commit 1a31fba

Browse files
committed
feat: AI chat landing page
update Ask AI styling and functionality to match
1 parent c2d5188 commit 1a31fba

File tree

7 files changed

+683
-209
lines changed

7 files changed

+683
-209
lines changed

app/api/chat/route.ts

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { OpenAI } from "openai";
33
import path from "path";
44

55
import { MessageRecord } from "@/components/ai/context";
6+
import { SUGGESTED_QUESTIONS } from "@/components/ai/suggestions";
67
import {
78
DocsEmbedding,
89
REJECTION_MESSAGE,
@@ -48,6 +49,48 @@ setInterval(() => {
4849
}
4950
}, MAX_SESSION_AGE);
5051

52+
/**
53+
* Generate contextual follow-up suggestions based on the conversation.
54+
* Uses OpenAI to pick 3 most relevant questions from the curated pool and rephrase them
55+
* to fit the conversation context.
56+
*/
57+
async function generateContextualSuggestions(
58+
client: OpenAI,
59+
conversationHistory: MessageRecord[]
60+
): Promise<string[]> {
61+
try {
62+
const response = await client.chat.completions.create({
63+
model: MODEL,
64+
messages: [
65+
{
66+
role: "system",
67+
content: `You are helping generate follow-up questions for a user chatting with the Recall documentation AI.
68+
69+
Here is a curated pool of key questions:
70+
${SUGGESTED_QUESTIONS.map((q, i) => `${i + 1}. ${q}`).join("\n")}
71+
72+
Based on the conversation history, pick 3 most relevant questions from this pool and rephrase them to fit the conversation context as potential follow-up questions shown to the user for their next input.
73+
74+
Return ONLY a JSON array of 3 strings, nothing else. Example: ["Question 1?", "Question 2?", "Question 3?"]`,
75+
},
76+
...conversationHistory,
77+
],
78+
temperature: 0.7,
79+
});
80+
81+
const content = response.choices[0]?.message?.content?.trim();
82+
if (!content) return [];
83+
84+
// Parse the JSON array response
85+
const suggestions = JSON.parse(content) as string[];
86+
return Array.isArray(suggestions) ? suggestions.slice(0, 3) : [];
87+
} catch (error) {
88+
console.error("Error generating suggestions:", error);
89+
// Fallback to random questions if generation fails
90+
return [...SUGGESTED_QUESTIONS].sort(() => Math.random() - 0.5).slice(0, 3);
91+
}
92+
}
93+
5194
export async function POST(request: Request) {
5295
if (!openai) {
5396
return new Response(JSON.stringify({ error: "OpenAI API key not configured" }), {
@@ -122,16 +165,21 @@ export async function POST(request: Request) {
122165
stream: true,
123166
});
124167

125-
// Create a transform stream to append source links
168+
// Create a transform stream to append source links and suggestions
126169
const transform = new TransformStream({
127170
transform(chunk, controller) {
128171
controller.enqueue(chunk);
129172
},
130-
flush(controller) {
173+
async flush(controller) {
131174
// Add reference links after the stream is done
132175
const referenceLinks = getReferenceLinks(relevant);
133-
const sourceJson = JSON.stringify({ references: referenceLinks }) + "\n"; // Match OpenAI response formatting
176+
const sourceJson = JSON.stringify({ references: referenceLinks }) + "\n";
134177
controller.enqueue(new TextEncoder().encode(sourceJson));
178+
179+
// Generate and add contextual suggestions
180+
const suggestions = await generateContextualSuggestions(openai, messages);
181+
const suggestionsJson = JSON.stringify({ suggestions }) + "\n";
182+
controller.enqueue(new TextEncoder().encode(suggestionsJson));
135183
},
136184
});
137185

app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
4848
buttonVariants({
4949
color: "secondary",
5050
}),
51-
"bg-secondary/50 text-fd-secondary-foreground/80 fixed right-4 bottom-4 z-10 gap-2 rounded-xl shadow-lg backdrop-blur-lg md:right-6 md:bottom-8"
51+
"bg-secondary/50 text-fd-secondary-foreground/80 fixed right-4 bottom-4 z-10 gap-2 shadow-lg backdrop-blur-lg md:right-6 md:bottom-8"
5252
)}
5353
>
5454
<MessageCircle className="size-4" />

components/ai/engines/openai.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ type ReferencesResponse = {
2121
references: MessageReference[];
2222
};
2323

24+
type SuggestionsResponse = {
25+
suggestions: string[];
26+
};
27+
2428
export async function createOpenAIEngine(): Promise<Engine> {
2529
const sessionId = localStorage.getItem("recallChatSessionId") || crypto.randomUUID();
2630
localStorage.setItem("recallChatSessionId", sessionId);
@@ -100,6 +104,11 @@ export async function createOpenAIEngine(): Promise<Engine> {
100104
message.references = data.references;
101105
onUpdate?.(content);
102106
}
107+
if ("suggestions" in json && !content.includes(REJECTION_MESSAGE)) {
108+
const data = json as SuggestionsResponse;
109+
message.suggestions = data.suggestions;
110+
onUpdate?.(content);
111+
}
103112
}
104113
}
105114

0 commit comments

Comments
 (0)