Skip to content

Commit

Permalink
init: article generation
Browse files Browse the repository at this point in the history
  • Loading branch information
PrinceBaghel258025 committed Sep 25, 2024
1 parent 1eaac53 commit 2340347
Show file tree
Hide file tree
Showing 12 changed files with 236 additions and 37 deletions.
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}}
ANTHROPIC_API_KEY: ${{secrets.ANTHROPIC_API_KEY}}
TURSO_DB_URL: ${{secrets.TURSO_DB_URL}}
TURSO_DB_AUTH_TOKEN: ${{secrets.TURSO_DB_AUTH_TOKEN}}
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 All @@ -95,7 +96,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 @@ -115,6 +116,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
77 changes: 77 additions & 0 deletions src/app/api/storm/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { serve } from "@upstash/qstash/nextjs";
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 } from "next/server";

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

const handler = serve(async (context) => {
const body = context.requestPayload as { topic: string };
const stormResponse = await context.run("Initiating Storm to generate article", async () => {
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}`,
},
},
);
return stormResponse.data;
});

await context.run("Saving Article to db", async () => {
const body = context.requestPayload as {
topic: string;
chatId: string;
orgId: string;
userId: string;
};
const chatId = body.chatId;
const orgId = body.orgId;
const userId = body.userId;
const latestResponse = {
id: nanoid(),
role: "assistant" as const,
content: stormResponse.content,
createdAt: new Date(),
audio: "",
};

await saveToDB({
_chat: [],
chatId: Number(chatId),
orgSlug: orgSlug as string,
latestResponse: latestResponse,
userId: userId,
orgId: orgId,
urlArray: urlArray,
});
});
});
return await handler(request);
};
12 changes: 12 additions & 0 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),
// Anthropic
ANTHROPIC_API_KEY: z.string().min(10),
// OpenAI
Expand Down Expand Up @@ -64,6 +70,12 @@ 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,
// Anthropic
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
// Clerk (Auth)
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 @@ -97,7 +97,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
104 changes: 80 additions & 24 deletions src/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,58 @@ 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 { mutate: InitArticleGeneration } = useMutation({
mutationFn: async ({
topic,
chatId,
orgId,
userId,
}: {
topic: string;
chatId: string;
orgId: string;
userId: string;
}) => {
const resp = await axios.post("/api/storm", {
topic: topic,
chatId: chatId,
orgId: orgId,
userId: userId,
});
return resp.data;
},
onSuccess: (data, vars, context) => {
soonerToast("Generating your article", {
description: "Please wait for 2 mins",
duration: 300 * 1000,
});

//TODO: set workflow id in state and make query to start invalidating
console.log("workflow id", data.workflowRunId);
},
onError: (error: any, vars, context) => {
console.error(error?.message);
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 +152,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 +353,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
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
8 changes: 8 additions & 0 deletions src/components/modelswitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { Button } from "@/components/button";
import { Cpu, Layers, PenTool, Settings } from "lucide-react";
import { ChatType } from "@/lib/types";
import { BookOpenText } from "@phosphor-icons/react";

export interface InputBarActionProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
Expand All @@ -34,6 +35,8 @@ const ModelSwitcher = React.forwardRef<HTMLButtonElement, InputBarActionProps>(
<Cpu className="h-4 w-4" />
) : chattype === "tldraw" ? (
<PenTool className="h-4 w-4" />
) : chattype === "storm" ? (
<BookOpenText className="h-4 w-4" />
) : (
<Settings className="h-4 w-4" />
);
Expand Down Expand Up @@ -62,6 +65,11 @@ const ModelSwitcher = React.forwardRef<HTMLButtonElement, InputBarActionProps>(
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="chat">Simple</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="ella">Ella</DropdownMenuRadioItem>
{isHome ? (
<DropdownMenuRadioItem value="storm">
Article
</DropdownMenuRadioItem>
) : null}
<DropdownMenuSeparator />
<DropdownMenuLabel inset>AIModels</DropdownMenuLabel>
</DropdownMenuRadioGroup>
Expand Down
Loading

0 comments on commit 2340347

Please sign in to comment.