diff --git a/client/src/features/core/api/get-dashboard-genai-insights.ts b/client/src/features/core/api/get-dashboard-genai-insights.ts new file mode 100644 index 0000000..b4edfae --- /dev/null +++ b/client/src/features/core/api/get-dashboard-genai-insights.ts @@ -0,0 +1,14 @@ +import { apiSlice } from "../../api/apiSlice"; +import { IResponse } from "../types"; + +interface Response extends IResponse { + data: string; +} + +export const { useGetInsightsQuery } = apiSlice.injectEndpoints({ + endpoints: (builder) => ({ + getInsights: builder.query({ + query: () => "/analytics/ai-insights", + }), + }), +}); diff --git a/client/src/features/core/components/GenAIInsight/index.tsx b/client/src/features/core/components/GenAIInsight/index.tsx new file mode 100644 index 0000000..e3d344a --- /dev/null +++ b/client/src/features/core/components/GenAIInsight/index.tsx @@ -0,0 +1,30 @@ +import { ReactNode } from "react"; +import { Callout, Flex, Text, Box } from "@radix-ui/themes"; +import { Link } from "react-router-dom"; + +type Props = { + insight: string | ReactNode; +}; + +export default function GenAIInsight({ insight }: Props) { + return ( + + + + + + + + AI Insights + + + + {insight} + + + + ); +} diff --git a/client/src/features/core/routes/home.tsx b/client/src/features/core/routes/home.tsx index 49a3b83..081ab74 100644 --- a/client/src/features/core/routes/home.tsx +++ b/client/src/features/core/routes/home.tsx @@ -2,21 +2,28 @@ import SummaryWidget from "../components/SummaryWidget"; import LastWeekSpendChart from "../components/LastWeekSpendChart"; import CategorySpendChart from "../components/CategorySpendChart"; import WidgetWrapper from "../components/WidgetWrapper"; +import GenAIInsight from "../components/GenAIInsight"; +import { useGetInsightsQuery } from "../api/get-dashboard-genai-insights"; -import { Grid } from "@radix-ui/themes"; +import { Grid, Spinner } from "@radix-ui/themes"; export default function HomePage() { + const { data, isLoading } = useGetInsightsQuery(); + return ( - - - - - - - - - - - + <> + : data?.data || ""} /> + + + + + + + + + + + + ); } diff --git a/client/src/features/core/types.ts b/client/src/features/core/types.ts index 91ec1cd..26d1cc2 100644 --- a/client/src/features/core/types.ts +++ b/client/src/features/core/types.ts @@ -1,5 +1,5 @@ export interface IResponse { status: string; message: string; - data: object | []; + data: unknown; } diff --git a/client/src/styles/custom.css b/client/src/styles/custom.css index ff208df..93503b1 100644 --- a/client/src/styles/custom.css +++ b/client/src/styles/custom.css @@ -11,7 +11,7 @@ min-height: 100%; height: 100vh; overflow: visible; - width: 960px; + max-width: 960px; margin: 0 auto 0; padding: 0; border-left: 0.1px solid var(--green-4); diff --git a/server/src/controllers/analytics/gen-ai.controller.ts b/server/src/controllers/analytics/gen-ai.controller.ts new file mode 100644 index 0000000..5892f39 --- /dev/null +++ b/server/src/controllers/analytics/gen-ai.controller.ts @@ -0,0 +1,22 @@ +import { Request, Response } from "express-serve-static-core"; +import { getAiInsights } from "../../services/management/analytics/gen-ai.service.js"; +import { createSuccessResponse } from "../../features/common/utils/response.js"; +import { getCategoryWiseSpend } from "../../services/management/analytics/dashboard-analytics.service.js"; + +const BASE_PROMPT = ` +Analyze my spending by category and generate +insights based on the same. Keep the insights simple +and limit the response to single paragraph. + +Here is the data: +{} + +Insights: + +` +export async function dashboardGenAIInsightsController(req: Request, res: Response) { + const categorySpend = await getCategoryWiseSpend() + const prompt = BASE_PROMPT.replace("{}", JSON.stringify(categorySpend)) + const insight = await getAiInsights(prompt) + return createSuccessResponse(req, res, 200, "", insight) +} \ No newline at end of file diff --git a/server/src/routes.ts b/server/src/routes.ts index 47bbe0d..af55a47 100644 --- a/server/src/routes.ts +++ b/server/src/routes.ts @@ -9,6 +9,7 @@ import { router as labelRouter } from "./features/label/routes.js"; import { router as recurringRouter } from './features/recurring/routes.js' import { router as managementRouter } from "./routes/management/credit-card-manager.routes.js"; import { router as analyticsRouter } from "./routes/analytics/dashboard-analytics.routes.js"; +import { router as genAiRouter } from "./routes/analytics/gen-ai.routes.js"; export const router: Router = express.Router() router.use(express.json()) @@ -22,3 +23,5 @@ router.use("/labels", labelRouter) router.use("/recurring", recurringRouter) router.use("/management/credit-card-settings", managementRouter) router.use("/analytics", analyticsRouter) +router.use("/analytics", analyticsRouter) +router.use("/analytics", genAiRouter) diff --git a/server/src/routes/analytics/gen-ai.routes.ts b/server/src/routes/analytics/gen-ai.routes.ts new file mode 100644 index 0000000..7c4e645 --- /dev/null +++ b/server/src/routes/analytics/gen-ai.routes.ts @@ -0,0 +1,9 @@ +import { + dashboardGenAIInsightsController, +} from "../../controllers/analytics/gen-ai.controller.js"; + +import express from "express"; + +export const router = express.Router() + +router.get("/ai-insights", dashboardGenAIInsightsController) diff --git a/server/src/services/management/analytics/gen-ai.service.ts b/server/src/services/management/analytics/gen-ai.service.ts new file mode 100644 index 0000000..f145e12 --- /dev/null +++ b/server/src/services/management/analytics/gen-ai.service.ts @@ -0,0 +1,21 @@ + +export async function getAiInsights(prompt: string) { + const body = { + model: "gemma:2b", + prompt: prompt, + stream: false, + } + const response = await fetch("http://localhost:11434/api/generate/", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(body) + }) + if (response.ok) { + const data = await response.json() + const insights = data["response"] + return insights + } + return null +} \ No newline at end of file