diff --git a/keep-ui/app/(keep)/incidents/[id]/chat/page.client.tsx b/keep-ui/app/(keep)/incidents/[id]/chat/page.client.tsx index b0e2447b2..e718b90a8 100644 --- a/keep-ui/app/(keep)/incidents/[id]/chat/page.client.tsx +++ b/keep-ui/app/(keep)/incidents/[id]/chat/page.client.tsx @@ -2,13 +2,27 @@ import { IncidentChat } from "./incident-chat"; import { IncidentDto } from "@/entities/incidents/model"; +import { EmptyStateCard } from "@/shared/ui"; +import { useConfig } from "@/utils/hooks/useConfig"; import { CopilotKit } from "@copilotkit/react-core"; +import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; export function IncidentChatClientPage({ incident, }: { incident: IncidentDto; }) { + const { data: config } = useConfig(); + if (config && !config.OPEN_AI_API_KEY_SET) { + return ( + + ); + } + return ( diff --git a/keep-ui/app/api/aws-marketplace/route.ts b/keep-ui/app/api/aws-marketplace/route.ts new file mode 100644 index 000000000..f8c4bb07c --- /dev/null +++ b/keep-ui/app/api/aws-marketplace/route.ts @@ -0,0 +1,21 @@ +import { NextRequest } from "next/server"; +import { redirect } from "next/navigation"; + +export async function POST(request: NextRequest) { + try { + // In App Router, we need to parse the request body manually + const body = await request.json(); + + const token = body["x-amzn-marketplace-token"]; + const offerType = body["x-amzn-marketplace-offer-type"]; + + // Base64 encode the token + const base64EncodedToken = encodeURIComponent(btoa(token)); + + // In App Router, we use the redirect function for redirects + return redirect(`/signin?amt=${base64EncodedToken}`); + } catch (error) { + console.error("Error processing request:", error); + return new Response("Bad Request", { status: 400 }); + } +} diff --git a/keep-ui/app/api/copilotkit/route.ts b/keep-ui/app/api/copilotkit/route.ts new file mode 100644 index 000000000..e8cde87f1 --- /dev/null +++ b/keep-ui/app/api/copilotkit/route.ts @@ -0,0 +1,41 @@ +import { + CopilotRuntime, + OpenAIAdapter, + copilotRuntimeNextJSAppRouterEndpoint, +} from "@copilotkit/runtime"; +import OpenAI, { OpenAIError } from "openai"; +import { NextRequest } from "next/server"; + +function initializeCopilotRuntime() { + try { + const openai = new OpenAI({ + organization: process.env.OPEN_AI_ORGANIZATION_ID, + apiKey: process.env.OPEN_AI_API_KEY, + }); + const serviceAdapter = new OpenAIAdapter({ openai }); + const runtime = new CopilotRuntime(); + return { runtime, serviceAdapter }; + } catch (error) { + if (error instanceof OpenAIError) { + console.log("Error connecting to OpenAI", error); + } else { + console.error("Error initializing Copilot Runtime", error); + } + return null; + } +} + +const runtimeOptions = initializeCopilotRuntime(); + +export const POST = async (req: NextRequest) => { + if (!runtimeOptions) { + return new Response("Error initializing Copilot Runtime", { status: 500 }); + } + const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({ + runtime: runtimeOptions.runtime, + serviceAdapter: runtimeOptions.serviceAdapter, + endpoint: "/api/copilotkit", + }); + + return handleRequest(req); +}; diff --git a/keep-ui/next-env.d.ts b/keep-ui/next-env.d.ts index 725dd6f24..40c3d6809 100644 --- a/keep-ui/next-env.d.ts +++ b/keep-ui/next-env.d.ts @@ -1,6 +1,5 @@ /// /// -/// // NOTE: This file should not be edited // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/keep-ui/package-lock.json b/keep-ui/package-lock.json index e58b5e758..551791941 100644 --- a/keep-ui/package-lock.json +++ b/keep-ui/package-lock.json @@ -53,7 +53,7 @@ "moment": "^2.29.4", "next": "^14.2.13", "next-auth": "^5.0.0-beta.25", - "openai": "^4.72.0", + "openai": "^4.73.0", "postcss": "^8.4.31", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", @@ -16064,9 +16064,9 @@ } }, "node_modules/openai": { - "version": "4.72.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.72.0.tgz", - "integrity": "sha512-hFqG9BWCs7L7ifrhJXw7mJXmUBr7d9N6If3J9563o0jfwVA4wFANFDDaOIWFdgDdwgCXg5emf0Q+LoLCGszQYA==", + "version": "4.73.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.73.0.tgz", + "integrity": "sha512-NZstV77w3CEol9KQTRBRQ15+Sw6nxVTicAULSjYO4wn9E5gw72Mtp3fAVaBFXyyVPws4241YmFG6ya4L8v03tA==", "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", diff --git a/keep-ui/package.json b/keep-ui/package.json index d06a62b64..42a3e8395 100644 --- a/keep-ui/package.json +++ b/keep-ui/package.json @@ -54,7 +54,7 @@ "moment": "^2.29.4", "next": "^14.2.13", "next-auth": "^5.0.0-beta.25", - "openai": "^4.72.0", + "openai": "^4.73.0", "postcss": "^8.4.31", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", diff --git a/keep-ui/pages/_error.jsx b/keep-ui/pages/_error.jsx deleted file mode 100644 index 46a61d690..000000000 --- a/keep-ui/pages/_error.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as Sentry from "@sentry/nextjs"; -import Error from "next/error"; - -const CustomErrorComponent = (props) => { - return ; -}; - -CustomErrorComponent.getInitialProps = async (contextData) => { - // In case this is running in a serverless function, await this in order to give Sentry - // time to send the error before the lambda exits - await Sentry.captureUnderscoreErrorException(contextData); - - // This will contain the status code of the response - return Error.getInitialProps(contextData); -}; - -export default CustomErrorComponent; diff --git a/keep-ui/pages/api/aws-marketplace.tsx b/keep-ui/pages/api/aws-marketplace.tsx deleted file mode 100644 index 8d5b8d203..000000000 --- a/keep-ui/pages/api/aws-marketplace.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - if (req.method === "POST") { - const { - "x-amzn-marketplace-token": token, - "x-amzn-marketplace-offer-type": offerType, - } = req.body; - - const base64EncodedToken = encodeURIComponent(btoa(token)); - - // Redirect to the sign-in page or wherever you want - // amt is amazon-marketplace-token - res.writeHead(302, { Location: `/signin?amt=${base64EncodedToken}` }); - res.end(); - } else { - // Handle any non-POST requests - res.status(405).send("Method Not Allowed"); - } -} diff --git a/keep-ui/pages/api/copilotkit.ts b/keep-ui/pages/api/copilotkit.ts deleted file mode 100644 index 2af1161ab..000000000 --- a/keep-ui/pages/api/copilotkit.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next"; -import { - CopilotRuntime, - OpenAIAdapter, - copilotRuntimeNextJSPagesRouterEndpoint, -} from "@copilotkit/runtime"; -import OpenAI from "openai"; - -const openai = new OpenAI({ - organization: process.env.OPEN_AI_ORGANIZATION_ID, - apiKey: process.env.OPEN_AI_API_KEY, -}); - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const serviceAdapter = new OpenAIAdapter({ openai }); - const runtime = new CopilotRuntime(); - - const handleRequest = copilotRuntimeNextJSPagesRouterEndpoint({ - endpoint: "/api/copilotkit", - runtime, - serviceAdapter, - }); - - return await handleRequest(req, res); -} diff --git a/keep-ui/shared/lib/server/getConfig.ts b/keep-ui/shared/lib/server/getConfig.ts index 7a823ed15..b10cbbe70 100644 --- a/keep-ui/shared/lib/server/getConfig.ts +++ b/keep-ui/shared/lib/server/getConfig.ts @@ -54,5 +54,6 @@ export function getConfig() { POSTHOG_HOST: process.env.POSTHOG_HOST, SENTRY_DISABLED: process.env.SENTRY_DISABLED, READ_ONLY: process.env.KEEP_READ_ONLY === "true", + OPEN_AI_API_KEY_SET: !!process.env.OPEN_AI_API_KEY, }; } diff --git a/keep-ui/shared/ui/EmptyState/EmptyStateCard.tsx b/keep-ui/shared/ui/EmptyState/EmptyStateCard.tsx new file mode 100644 index 000000000..c683e3e0e --- /dev/null +++ b/keep-ui/shared/ui/EmptyState/EmptyStateCard.tsx @@ -0,0 +1,36 @@ +import { Card } from "@tremor/react"; +import { CircleStackIcon } from "@heroicons/react/24/outline"; +import clsx from "clsx"; + +export function EmptyStateCard({ + title, + icon, + description, + className, + children, +}: { + icon?: React.ElementType; + title: string; + description: string; + className?: string; + children?: React.ReactNode; +}) { + const Icon = icon || CircleStackIcon; + return ( + +
+ +

+ {title} +

+

+ {description} +

+ {children} +
+
+ ); +} diff --git a/keep-ui/shared/ui/EmptyState/index.ts b/keep-ui/shared/ui/EmptyState/index.ts new file mode 100644 index 000000000..a31d06c86 --- /dev/null +++ b/keep-ui/shared/ui/EmptyState/index.ts @@ -0,0 +1 @@ +export { EmptyStateCard } from "./EmptyStateCard"; diff --git a/keep-ui/shared/ui/index.ts b/keep-ui/shared/ui/index.ts index 4cfab5810..307db1bbb 100644 --- a/keep-ui/shared/ui/index.ts +++ b/keep-ui/shared/ui/index.ts @@ -2,3 +2,4 @@ export { TablePagination } from "./TablePagination"; export { TabLinkNavigation, TabNavigationLink } from "./TabLinkNavigation"; export { DateTimeField } from "./DateTimeField"; export { FieldHeader } from "./FieldHeader"; +export { EmptyStateCard } from "./EmptyState"; diff --git a/keep-ui/types/internal-config.ts b/keep-ui/types/internal-config.ts index 550828b27..c0d13710b 100644 --- a/keep-ui/types/internal-config.ts +++ b/keep-ui/types/internal-config.ts @@ -18,4 +18,5 @@ export interface InternalConfig { SENTRY_DISABLED: string; // READ ONLY READ_ONLY: boolean; + OPEN_AI_API_KEY_SET: boolean; }