Skip to content
Open
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
1 change: 1 addition & 0 deletions T049_SPD/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
Binary file added T049_SPD/T049_SPD_presentation.pdf
Binary file not shown.
90 changes: 90 additions & 0 deletions T049_SPD/app/ai/cropAdviceChatbot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { z } from "genkit";
import { ai } from "./genkit.js";

const CropAdviceChatbotInputSchema = z.object({
query: z
.string()
.describe(
"The farmer's question about crop selection, planting, or storage."
),
language: z
.string()
.optional()
.describe(
"The language for the response. Should be a language spoken in India."
),
});

const CropAdviceChatbotOutputSchema = z.object({
answer: z.string().describe("The chatbot's answer to the farmer's question."),
});

async function cropAdviceChatbot(input) {
return cropAdviceChatbotFlow(input);
}

const GetWeatherConditionsInputSchema = z.object({
location: z.string().describe("The location to get weather conditions for."),
});

const GetWeatherConditionsOutputSchema = z.object({
temperature: z.number().describe("The current temperature in Celsius."),
humidity: z.number().describe("The current humidity percentage."),
description: z
.string()
.describe("A brief description of the weather conditions."),
});

const getWeatherConditions = ai.defineTool(
{
name: "getWeatherConditions",
description: "Returns the current weather conditions for a given location.",
inputSchema: GetWeatherConditionsInputSchema,
outputSchema: GetWeatherConditionsOutputSchema,
},
async (input) => {
// TODO: Implement fetching weather data from OpenWeatherMap API or Agro APIs
// For now, return dummy data
return {
temperature: 25,
humidity: 70,
description: "Sunny with a gentle breeze.",
};
}
);

const prompt = ai.definePrompt({
name: "cropAdviceChatbotPrompt",
model: "googleai/gemini-2.5-flash",
tools: [getWeatherConditions],
input: { schema: CropAdviceChatbotInputSchema },
output: { schema: CropAdviceChatbotOutputSchema },
system: `You are a helpful AI chatbot assisting farmers with their agricultural questions. Answer the user's question to the best of your ability.
If the user asks about what crops to grow, planting advice, or general farming practices, consider using the getWeatherConditions tool to provide more relevant and helpful information.`,
prompt: `
{{#if language}}
Please provide the answer in the following language: {{{language}}}.
{{else}}
Please provide the answer in English.
{{/if}}

Question: {{{query}}}
`,
});

const cropAdviceChatbotFlow = ai.defineFlow(
{
name: "cropAdviceChatbotFlow",
inputSchema: CropAdviceChatbotInputSchema,
outputSchema: CropAdviceChatbotOutputSchema,
},
async (input) => {
const { output } = await prompt(input);
return output;
}
);
export {
cropAdviceChatbot,
CropAdviceChatbotInputSchema,
CropAdviceChatbotOutputSchema,
};
11 changes: 11 additions & 0 deletions T049_SPD/app/ai/genkit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { genkit } from "genkit";
import { googleAI } from "@genkit-ai/googleai";

export const ai = genkit({
plugins: [
googleAI({
apiKey: process.env.GOOGLE_API_KEY,
}),
],
model: "googleai/gemini-2.5-flash",
});
69 changes: 69 additions & 0 deletions T049_SPD/app/ai/text-to-speech.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { ai } from "./genkit.js";
import { googleAI } from "@genkit-ai/googleai";
import { z } from "genkit";
import wav from "wav";

export const TextToSpeechInputSchema = z.string();

export const TextToSpeechOutputSchema = z
.string()
.describe(
"The generated audio as a data URI. Expected format: 'data:audio/wav;base64,<encoded_data>'"
);

export async function textToSpeech(input) {
return textToSpeechFlow(input);
}

async function toWav(pcmData, channels = 1, rate = 24000, sampleWidth = 2) {
return new Promise((resolve, reject) => {
const writer = new wav.Writer({
channels,
sampleRate: rate,
bitDepth: sampleWidth * 8,
});

let bufs = [];
writer.on("error", reject);
writer.on("data", function (d) {
bufs.push(d);
});
writer.on("end", function () {
resolve(Buffer.concat(bufs).toString("base64"));
});

writer.write(pcmData);
writer.end();
});
}

const textToSpeechFlow = ai.defineFlow(
{
name: "textToSpeechFlow",
inputSchema: TextToSpeechInputSchema,
outputSchema: TextToSpeechOutputSchema,
},
async (query) => {
const { media } = await ai.generate({
model: googleAI.model("gemini-2.5-flash-preview-tts"),
config: {
responseModalities: ["AUDIO"],
speechConfig: {
voiceConfig: {
prebuiltVoiceConfig: { voiceName: "Algenib" },
},
},
},
prompt: query,
});
if (!media) {
throw new Error("No media returned from TTS model.");
}
const audioBuffer = Buffer.from(
media.url.substring(media.url.indexOf(",") + 1),
"base64"
);
const wavBase64 = await toWav(audioBuffer);
return "data:audio/wav;base64," + wavBase64;
}
);
18 changes: 18 additions & 0 deletions T049_SPD/app/components/bootstrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { auth } from "express-openid-connect";
import { oidcConfig } from "../config/auth0.js";
import authRouter from "./routes/auth.routes.js";
import soilHealthRouter from "./routes/soilHealth.routes.js";
import weatherRouter from "./routes/weather.routes.js";
import marketPriceRouter from "./routes/marketPrice.routes.js";
import chatBotRouter from "./routes/chatBot.routes.js";

const bootstrap = (app) => {
app.use(auth(oidcConfig));
app.use("/api/auth", authRouter);
app.use("/api/soilHealth", soilHealthRouter);
app.use("/api/weather", weatherRouter);
app.use("/api/marketPrice", marketPriceRouter);
app.use("/api/chatBot", chatBotRouter);
};

export default bootstrap;
116 changes: 116 additions & 0 deletions T049_SPD/app/components/controllers/auth.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import UserModel from "../../db/models/user.model.js";
import asyncHandler from "../utils/asyncHandler.js";
import { signToken } from "../utils/JWT.js";
import bcrypt from "bcrypt";

const register = asyncHandler(async (req, res) => {
const { name, email, password } = req.body;
if (!email || !password)
return res.status(400).json({ error: "Email and password required" });

const existing = await UserModel.findOne({ email: email.toLowerCase() });
if (existing)
return res.status(400).json({ error: "Email already registered" });

const saltRounds = 12;
const passwordHash = await bcrypt.hash(password, saltRounds);

const user = new UserModel({
authProvider: "local",
name,
email: email.toLowerCase(),
passwordHash,
});

await user.save();

const token = signToken(user);
res.json({
token,
user: { id: user._id, email: user.email, name: user.name },
});
});

const login = asyncHandler(async (req, res) => {
const { email, password } = req.body;
if (!email || !password)
return res.status(400).json({ error: "Email and password required" });

// include passwordHash for checking
const user = await UserModel.findOne({
email: email.toLowerCase(),
authProvider: "local",
}).select("+passwordHash");
if (!user) return res.status(401).json({ error: "Invalid credentials" });

const ok = await bcrypt.compare(password, user.passwordHash);
if (!ok) return res.status(401).json({ error: "Invalid credentials" });

const token = signToken(user);
res.json({
token,
user: { id: user._id, email: user.email, name: user.name },
});
});

const auth0_redirect = asyncHandler(async (req, res) => {
// express-openid-connect attaches req.oidc.user when authenticated
if (!req.oidc?.isAuthenticated()) {
return res.status(401).json({ error: "Not authenticated with Auth0" });
}

const oidcUser = req.oidc.user;
// oidcUser contains claims like sub, email, name, picture
const auth0Id = oidcUser.sub;
const email = (oidcUser.email || "").toLowerCase();

// Find or create local user record for this Auth0 user
let user = await UserModel.findOne({ auth0Id, authProvider: "auth0" });
if (!user) {
// If an account with same email exists as local, you may want to decide whether to merge or reject.
// Here we prefer linking to a new auth0 record if same email exists with local provider.
const existingLocal = await UserModel.findOne({ email });
if (existingLocal) {
// Option: link accounts (set auth0Id and change provider?) — here we keep separate and refuse linking automatically.
// For clarity we will not auto-link; inform the user.
// You can implement a linking flow (ask user for password then attach auth0Id).
// For demonstration, we still create a new record with auth0 provider and same email (unique constraint will fail).
// So we'll instead return an error asking the client to link accounts manually.
return res.status(409).json({
error:
"An account with this email already exists as local. Implement a linking flow to connect Auth0 to your existing account.",
});
}

user = new UserModel({
authProvider: "auth0",
auth0Id,
email,
name: oidcUser.name || oidcUser.nickname || "",
picture: oidcUser.picture,
});

await user.save();
}

// issue our JWT
const token = signToken(user);

// Option A: respond JSON (for SPA clients)
return res.json({
token,
user: { id: user._id, email: user.email, name: user.name },
});

// Option B: redirect to your front-end and include token as query fragment (less recommended)
// res.redirect(`${FRONTEND_URL}/auth/success#token=${token}`);
});

const getProfile = asyncHandler(async (req, res) => {
const userId = req.user.sub; // set by middleware
const user = await UserModel.findById(userId).select("-passwordHash -__v");
if (!user) return res.status(404).json({ error: "User not found" });
res.json({ user });
});

export { register, login, auth0_redirect, getProfile };
Loading