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

Article Generation #99

Merged
merged 3 commits into from
Oct 17, 2024
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
6 changes: 6 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ jobs:
- name: Lint and fix
run: npm run lint:fix
env:
STORM_ENDPOINT: ${{secrets.STORM_ENDPOINT}}
QSTASH_TOKEN: ${{secrets.QSTASH_TOKEN}}
KEYCLOAK_CLIENT_ID: ${{secrets.KEYCLOAK_CLIENT_ID}}
KEYCLOAK_CLIENT_SECRET: ${{secrets.KEYCLOAK_CLIENT_SECRET}}
KEYCLOAK_BASE_URL: ${{secrets.KEYCLOAK_BASE_URL}}
KEYCLOAK_REALM: ${{secrets.KEYCLOAK_REALM}}
LITELLM_BASE_URL: ${{secrets.LITELLM_BASE_URL}}
LITELLM_API_KEY: ${{secrets.LITELLM_API_KEY}}
ANTHROPIC_API_KEY: ${{secrets.ANTHROPIC_API_KEY}}
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"@types/react-dom": "18.0.11",
"@uidotdev/usehooks": "2.4.1",
"@uploadthing/react": "5.2.0",
"@upstash/qstash": "^2.7.9",
"@upstash/redis": "1.24.3",
"ably": "1.2.48",
"ai": "2.2.20",
Expand Down Expand Up @@ -97,7 +98,7 @@
"langsmith": "0.0.48",
"lucide-react": "0.228.0",
"next": "14.0.0",
"next-themes": "0.2.1",
"next-themes": "^0.3.0",
"next-transpile-modules": "^10.0.1",
"next-usequerystate": "1.13.1",
"openai": "4.17.4",
Expand All @@ -117,6 +118,7 @@
"remark-gfm": "3.0.1",
"remark-math": "^6.0.0",
"remark-rehype": "10.1.0",
"sonner": "^1.5.0",
"superagentai-js": "0.1.44",
"tailwind-merge": "1.12.0",
"tailwindcss-animate": "1.0.5",
Expand Down
65 changes: 65 additions & 0 deletions src/app/api/storm/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import axios from "axios";
import { env } from "@/app/env.mjs";
import { saveToDB } from "@/utils/apiHelper";
import { auth } from "@clerk/nextjs";
import { nanoid } from "ai";
import { NextRequest, NextResponse } from "next/server";

export const POST = async (request: NextRequest) => {
const url = request.url;
const urlArray = url.split("/");
const { orgSlug } = auth();
const body = await request.json();

const response = await axios.post(
`${env.KEYCLOAK_BASE_URL}/realms/${env.KEYCLOAK_REALM}/protocol/openid-connect/token`,
new URLSearchParams({
client_id: env.KEYCLOAK_CLIENT_ID,
client_secret: env.KEYCLOAK_CLIENT_SECRET,
grant_type: "client_credentials",
}),
{
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
},
);
const accessToken = response.data.access_token;

const stormResponse = await axios.post(
`${env.STORM_ENDPOINT}`,
{
topic: body.topic,
search_top_k: 5,
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
);

const chatId = body.chatId;
const orgId = body.orgId;
const userId = body.userId;
const latestResponse = {
id: nanoid(),
role: "assistant" as const,
content: stormResponse?.data?.content,
createdAt: new Date(),
audio: "",
};

await saveToDB({
_chat: [],
chatId: Number(chatId),
orgSlug: orgSlug as string,
latestResponse: latestResponse,
userId: userId,
orgId: orgId,
urlArray: urlArray,
});
return NextResponse.json({
data: stormResponse.data,
});
};
15 changes: 13 additions & 2 deletions src/app/env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import { z } from "zod";

export const env = createEnv({
server: {
STORM_ENDPOINT: z.string().min(1),
QSTASH_TOKEN: z.string().min(1),
KEYCLOAK_CLIENT_ID: z.string().min(1),
KEYCLOAK_CLIENT_SECRET: z.string().min(1),
KEYCLOAK_BASE_URL: z.string().min(1),
KEYCLOAK_REALM: z.string().min(1),
// LITELLM
LITELLM_BASE_URL: z.string().min(1),
LITELLM_API_KEY: z.string().min(10),
Expand Down Expand Up @@ -67,10 +73,15 @@ export const env = createEnv({
},

runtimeEnv: {
STORM_ENDPOINT: process.env.STORM_ENDPOINT,
QSTASH_TOKEN: process.env.QSTASH_TOKEN,
KEYCLOAK_CLIENT_ID: process.env.KEYCLOAK_CLIENT_ID,
KEYCLOAK_CLIENT_SECRET: process.env.KEYCLOAK_CLIENT_SECRET,
KEYCLOAK_BASE_URL: process.env.KEYCLOAK_BASE_URL,
KEYCLOAK_REALM: process.env.KEYCLOAK_REALM,
// LITELLM
LITELLM_BASE_URL: process.env.LITELLM_BASE_URL,
LITELLM_API_KEY: process.env.LITELLM_API_KEY,
// Anthropic
LITELLM_API_KEY: process.env.LITELLM_API_KEY, // Anthropic
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
// Clerk (Auth)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY:
Expand Down
2 changes: 2 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { dark } from "@clerk/themes";
import { Inter } from "next/font/google";
import QueryProviders from "@/app/queryProvider";
import { Toaster } from "@/components/ui/toaster";
import { Toaster as SoonerToaster } from "@/components/ui/sooner";
import { Providers } from "@/app/providers";
const inter = Inter({ subsets: ["latin"] });

Expand Down Expand Up @@ -260,6 +261,7 @@ export default function RootLayout({
>
{children}
<Toaster />
<SoonerToaster />
</div>
</QueryProviders>
</Providers>
Expand Down
5 changes: 4 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ export default function Home() {
try {
const res = await fetch(`/api/generateNewChatId/${orgId}`, {
method: "POST",
body: JSON.stringify({ type: chatType || "chat" }),
body:
chatType === "storm"
? JSON.stringify({ type: chatType || "chat", title: input })
: JSON.stringify({ type: chatType || "chat" }),
});
const data = await res.json();

Expand Down
107 changes: 82 additions & 25 deletions src/components/chat.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { useState, useEffect, useCallback, useRef } from "react";
import { ChatType } from "@/lib/types";
import InputBar from "@/components/inputBar";
import { Message, useChat } from "ai/react";
Expand All @@ -15,6 +15,7 @@ import { useDropzone } from "react-dropzone";
import { X } from "lucide-react";
import { useImageState } from "@/store/tlDrawImage";
import { useQueryState } from "next-usequerystate";
import { toast as soonerToast } from "sonner";

interface ChatProps {
orgId: string;
Expand Down Expand Up @@ -52,6 +53,59 @@ export default function Chat(props: ChatProps) {
const [chattype, setChattype] = useState<ChatType>(
props?.type || incomingModel || "chat",
);
const [isNewChat, setIsNewChat] = useQueryState("new");
const [incomingInput] = useQueryState("input");
const soonToastRef = useRef<number | string | undefined>();
const { mutate: InitArticleGeneration } = useMutation({
mutationFn: async ({
topic,
chatId,
orgId,
userId,
}: {
topic: string;
chatId: string;
orgId: string;
userId: string;
}) => {
soonToastRef.current = soonerToast("Generating your article", {
description: "Please wait for 2 mins",
duration: 300 * 1000,
});
console.log("storm");
const resp = await axios.post("/api/storm", {
topic: topic,
chatId: chatId,
orgId: orgId,
userId: userId,
});
return resp.data;
},
onSuccess: (data, vars, context) => {
//TODO: set workflow id in state and make query to start invalidating
soonerToast.dismiss(soonToastRef.current);
},
onError: (error: any, vars, context) => {
soonerToast.dismiss(soonToastRef.current);
soonerToast("Something went wrong ", {
description: "Sunday, December 03, 2023 at 9:00 AM",
duration: 5 * 1000,
});
},
});

useEffect(() => {
if (isNewChat === "true" && incomingInput && incomingModel === "storm") {
// make a call to storm endpoint
InitArticleGeneration({
chatId: props.chatId,
topic: incomingInput,
orgId: props.orgId,
userId: props.uid,
});
setIsNewChat("false");
}
}, [isNewChat]);

const onDrop = useCallback(async (acceptedFiles: File[]) => {
if (acceptedFiles && acceptedFiles[0]?.type.startsWith("image/")) {
Expand Down Expand Up @@ -99,6 +153,7 @@ export default function Chat(props: ChatProps) {
} = useQuery({
queryKey: ["chats", props.chatId],
queryFn: chatFetcher,
refetchInterval: chattype === "storm" ? 50 * 1000 : false,
initialData: props.dbChat,
refetchOnWindowFocus: false,
});
Expand Down Expand Up @@ -299,30 +354,32 @@ export default function Chat(props: ChatProps) {
/>
</div>
)}
<InputBar
onDrop={onDrop}
getInputProps={getInputProps}
getRootProps={getRootProps}
onClickOpenChatSheet={props.onClickOpenChatSheet}
onClickOpen={open}
dropZoneImage={image}
dropZoneActive={dropZoneActive}
setDropzoneActive={setDropzoneActive}
chatId={props.chatId}
orgId={props.orgId}
messages={messages}
setMessages={setMessages}
username={props.username}
userId={props.uid}
chattype={chattype}
setChattype={setChattype}
value={input}
onChange={handleInputChange}
setInput={setInput}
append={append}
isChatCompleted={isChatCompleted}
isLoading={isLoading}
/>
{chattype !== "storm" ? (
<InputBar
onDrop={onDrop}
getInputProps={getInputProps}
getRootProps={getRootProps}
onClickOpenChatSheet={props.onClickOpenChatSheet}
onClickOpen={open}
dropZoneImage={image}
dropZoneActive={dropZoneActive}
setDropzoneActive={setDropzoneActive}
chatId={props.chatId}
orgId={props.orgId}
messages={messages}
setMessages={setMessages}
username={props.username}
userId={props.uid}
chattype={chattype}
setChattype={setChattype}
value={input}
onChange={handleInputChange}
setInput={setInput}
append={append}
isChatCompleted={isChatCompleted}
isLoading={isLoading}
/>
) : null}
</>
)}
</div>
Expand Down
18 changes: 17 additions & 1 deletion src/components/chatcard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from "@/components/card";
import Chatusers, { getUserIdList } from "@/components/chatusersavatars";
import { CircleNotch } from "@phosphor-icons/react";
import { ChatEntry, ChatLog } from "@/lib/types";
import { ChatEntry, ChatLog, ChatType } from "@/lib/types";
import Image from "next/image";
import AudioButton from "@/components//audioButton";
import { useRouter } from "next/navigation";
Expand All @@ -30,6 +30,22 @@ type Props = {
isHome?: boolean;
};

const getChatType = (type: ChatType) => {
if (type === "tldraw") {
return "Tldraw";
} else if (type === "advanced") {
return "Advanced";
} else if (type === "ella") {
return "Ella";
} else if (type === "rag") {
return "Rag";
} else if (type === "storm") {
return "Article";
} else {
return "Simple Chat";
}
};

const Chatcard = ({
chat,
uid,
Expand Down
11 changes: 1 addition & 10 deletions src/components/inputBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ const InputBar = (props: InputBarProps) => {
useEffect(() => {
if (isNewChat === "true" && incomingInput) {
//TODO: use types for useQueryState
if (incomingInput && chattype !== "tldraw") {
if (incomingInput && chattype !== "tldraw" && chattype !== "storm") {
const params = new URLSearchParams(window.location.search);
if (
params.get("imageUrl") &&
Expand All @@ -259,16 +259,7 @@ const InputBar = (props: InputBarProps) => {
setIsFromClipboard("false");
setIsNewChat("false");
}, [isFromClipboard, isNewChat]);
// const ably = useAbly();

// console.log(
// "ably",
// ably.channels
// .get(`channel_${props.chatId}`)
// .presence.get({ clientId: `room_${props.chatId}` }),
// );

// const { presenceData, updateStatus } = usePresence(`channel_${props.chatId}`);
const preferences = usePreferences();
const { presenceData, updateStatus } = usePresence(
`channel_${props.chatId}`,
Expand Down
4 changes: 4 additions & 0 deletions src/components/inputBar2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import ModelSwitcher from "./modelswitcher";
import VadAudio from "./VadAudio";
import { useDropzone } from "react-dropzone";
import { X } from "lucide-react";
import { parseAsString, useQueryState } from "next-usequerystate";
const isValidImageType = (value: string) =>
/^image\/(jpeg|png|jpg|webp)$/.test(value);

Expand Down Expand Up @@ -102,6 +103,7 @@ const InputBar = (props: InputBarProps) => {
const [disableInputs, setDisableInputs] = useState<boolean>(false);
const [isRagLoading, setIsRagLoading] = useState<boolean>(false);
const queryClient = useQueryClient();
const [chatType] = useQueryState("model", parseAsString.withDefault("chat"));

const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
Expand Down Expand Up @@ -449,6 +451,8 @@ const InputBar = (props: InputBarProps) => {
? ""
: props.dropZoneActive
? "Ask question about image"
: chatType === "storm"
? "Enter the topic"
: "Type your message here..."
}
autoFocus
Expand Down
Loading
Loading