Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Related topics feature #3

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
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>
);
}