Skip to content
50 changes: 50 additions & 0 deletions app/api/getRelatedTopics/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { NextResponse } from "next/server";

export async function POST(request: Request) {
const { topic } = await request.json();
try {
// Create a prompt for the model to generate related topics
const prompt = `Fill in this template with appropriate values: { "topic": "${topic}", "related_topics": [FILL_TOPIC1,FILL_TOPIC2, FILL_TOPIC3] }`;
const model = "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo";

// Make the call to generate topics
const res = await fetch("https://together.helicone.ai/v1/completions", {
headers: {
"Content-Type": "application/json",
"Helicone-Auth": `Bearer ${process.env.HELICONE_API_KEY}`,
Authorization: `Bearer ${process.env.TOGETHER_API_KEY ?? ""}`,
},
method: "POST",
body: JSON.stringify({ prompt, model }),
});

const data = await res.json();
const choices = data.choices;
let topics = [];

// Check if there are choices and extract related topics
if (choices && choices.length > 0 && choices[0].text) {
const text = choices[0].text;
// Use a regular expression to find JSON-like content
const jsonMatch = text.match(/\{[^]*\}/);

if (jsonMatch) {
try {
const jsonContent = JSON.parse(jsonMatch[0]);
// Ensure related_topics is an array before assigning
if (jsonContent.related_topics && Array.isArray(jsonContent.related_topics)) {
topics = jsonContent.related_topics.slice(0, 3); // Limit to a maximum of 3 related topics
}
} catch (parseError) {
console.error("Failed to parse JSON:", parseError);
}
}
}
return NextResponse.json({ topics });
} catch (e) {
console.error(e);
return new Response("Error. Failed to generate related topics.", {
status: 500,
});
}
}
16 changes: 10 additions & 6 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import Sources from "@/components/Sources";
import { useState } from "react";
import {
createParser,
ParsedEvent,
ReconnectInterval,
type ParsedEvent,
type ReconnectInterval,
} from "eventsource-parser";
import { getSystemPrompt } from "@/utils/utils";
import Chat from "@/components/Chat";
Expand All @@ -25,13 +25,14 @@ export default function Home() {
const [loading, setLoading] = useState(false);
const [ageGroup, setAgeGroup] = useState("Middle School");

const handleInitialChat = async () => {
const handleInitialChat = async (newTopic?: string) => {
setShowResult(true);
setLoading(true);
setTopic(inputValue);
setMessages([]);
setTopic(newTopic ?? inputValue);
setInputValue("");

await handleSourcesAndChat(inputValue);
await handleSourcesAndChat(newTopic ?? inputValue);

setLoading(false);
};
Expand Down Expand Up @@ -134,7 +135,9 @@ export default function Home() {
<Header />

<main
className={`flex grow flex-col px-4 pb-4 ${showResult ? "overflow-hidden" : ""}`}
className={`flex grow flex-col px-4 pb-4 ${
showResult ? "overflow-hidden" : ""
}`}
>
{showResult ? (
<div className="mt-2 flex w-full grow flex-col justify-between overflow-hidden">
Expand All @@ -147,6 +150,7 @@ export default function Home() {
setPromptValue={setInputValue}
setMessages={setMessages}
handleChat={handleChat}
handleInitialChat={handleInitialChat}
topic={topic}
/>
<Sources sources={sources} isLoading={isLoadingSources} />
Expand Down
17 changes: 14 additions & 3 deletions components/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import FinalInputArea from "./FinalInputArea";
import { useEffect, useRef, useState } from "react";
import simpleLogo from "../public/simple-logo.png";
import Image from "next/image";
import RelatedTopics from "./RelatedTopics";

export default function Chat({
messages,
Expand All @@ -12,6 +13,7 @@ export default function Chat({
setMessages,
handleChat,
topic,
handleInitialChat,
}: {
messages: { role: string; content: string }[];
disabled: boolean;
Expand All @@ -22,6 +24,7 @@ export default function Chat({
>;
handleChat: () => void;
topic: string;
handleInitialChat: () => Promise<void>;
}) {
const messagesEndRef = useRef<HTMLDivElement>(null);
const scrollableContainerRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -60,7 +63,7 @@ export default function Chat({

return (
<div className="flex grow flex-col gap-4 overflow-hidden">
<div className="flex grow flex-col overflow-hidden lg:p-4">
<div className="flex grow flex-col overflow-hidden lg:px-4">
<p className="uppercase text-gray-900">
<b>Topic: </b>
{topic}
Expand Down Expand Up @@ -99,7 +102,9 @@ export default function Chat({
{Array.from(Array(10).keys()).map((i) => (
<div
key={i}
className={`${i < 5 && "hidden sm:block"} h-10 animate-pulse rounded-md bg-gray-300`}
className={`${
i < 5 && "hidden sm:block"
} h-10 animate-pulse rounded-md bg-gray-300`}
style={{ animationDelay: `${i * 0.05}s` }}
/>
))}
Expand All @@ -108,7 +113,13 @@ export default function Chat({
</div>
</div>

<div className="bg-white lg:p-4">
<RelatedTopics
topic={topic}
setPromptValue={setPromptValue}
handleInitialChat={handleInitialChat}
/>

<div className="bg-white lg:px-4">
<FinalInputArea
disabled={disabled}
promptValue={promptValue}
Expand Down
75 changes: 75 additions & 0 deletions components/RelatedTopics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type React from "react";
import { Fragment, useEffect, useState } from "react";

export default function RelatedTopics({
topic,
setPromptValue,
handleInitialChat,
}: {
topic: string;
setPromptValue: React.Dispatch<React.SetStateAction<string>>;
handleInitialChat: (newTopic?: string) => Promise<void>;
}) {
const [relatedTopics, setRelatedTopics] = useState<string[]>([]);
const [loading, setLoading] = useState<boolean>(false);

useEffect(() => {
// Function to fetch related topics from the API
const fetchRelatedTopics = async () => {
setLoading(true);
try {
const response = await fetch("/api/getRelatedTopics", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ topic }),
});

if (response.ok) {
const data = await response.json();
setRelatedTopics(data.topics);
}
} catch (error) {
console.error("Failed to fetch related topics:", error);
} finally {
setLoading(false);
}
};

if (topic) {
fetchRelatedTopics(); // Fetch topics if a topic is provided
}
}, [topic]);

// Function to handle click on a related topic
const handleTopicClick = (relatedTopic: string) => {
setPromptValue(relatedTopic); // Set the prompt value to the clicked topic
handleInitialChat(relatedTopic); // Initiate chat with the clicked topic
};

return (
<div className="flex gap-2 lg:px-4">
<h3 className="text-base font-bold uppercase leading-[152.5%] text-black text-nowrap">
Related Topics:
</h3>
{loading ? (
<p>Loading...</p>
) : relatedTopics.length > 0 ? (
<ul className="flex gap-4 lg:pl-2 overflow-x-scroll text-nowrap">
{relatedTopics.map((relatedTopic, index) => (
<Fragment key={relatedTopic}>
{index > 0 && "|"}
<li
onClick={() => handleTopicClick(relatedTopic)}
className="text-blue-500 hover:cursor-pointer"
>
{relatedTopic}
</li>
</Fragment>
))}
</ul>
) : null}
</div>
);
}