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
18 changes: 10 additions & 8 deletions src/components/rating/Rating.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from "react";

import { ThumbDownIcon, ThumbUpIcon } from "@/chakra/custom-chakra-icons";
import { SupaBaseDatabase } from "@/database/database";
import { isSupabaseInitialized, addFeedback } from "@/database/database";
import { AnswerQuality, FeedbackPayload, Ratings } from "@/types";
import { Button, Flex, Text } from "@chakra-ui/react";

Expand Down Expand Up @@ -52,15 +52,17 @@ const Rating = ({ isResponseGenerated, feedbackId }: RatingProps) => {
if (feedback.rating === Ratings.NEGATIVE && !feedback.answerQuality) {
return;
}
const { status, error } = await SupaBaseDatabase.getInstance().addFeedback({
...feedback,
feedbackId,
});

if (status >= 200 && status < 300 && !error) {
setIsFeedbackSubmitted(true);
console.log("Feedback sent successfully");
if (isSupabaseInitialized) {
// TODO: Since this function returns true or false, we should assign the
// return value to a variable and check if the feedback was submitted
await addFeedback(feedback, feedbackId);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this function returns true or false, we should assign the return value a variable and check if the feedback was submitted. Only then should we setFeedBackSubmitted to true

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, this is a hack to get the page to close.

} else {
console.error('Cannot submit rating because supabase is not initialized');
}
// TODO: properly replace this to close negative ratings
// without indicating it was properly sent
setIsFeedbackSubmitted(true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to do this if the db isn't initialised

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's the only way that the page closes

};

if (!isResponseGenerated) {
Expand Down
100 changes: 91 additions & 9 deletions src/database/database.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FeedbackPayload } from "@/types";
import { createClient } from "@supabase/supabase-js";
import { createClient, SupabaseClient } from "@supabase/supabase-js";
import getConfig from "next/config";

// Access the environment variables
Expand All @@ -8,17 +8,40 @@ const SUPABASE_URL = publicRuntimeConfig.SUPABASE_URL;
const SUPABASE_ANON_KEY = publicRuntimeConfig.SUPABASE_ANON_KEY;
const DB_NAME = publicRuntimeConfig.DB_NAME;

// Initialize Supabase client
let supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
let supabase: SupabaseClient | null;

// Empty string and undefined will both resolve to false
const hasCredentials = SUPABASE_URL?.trim() && SUPABASE_ANON_KEY?.trim()

try {
if (!hasCredentials) {
throw new Error("Credentials are missing")
}
supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
} catch (err) {
supabase = null
console.error(err)
}

export const isSupabaseInitialized = Boolean(supabase)

// Example usage: Fetch all rows from a table named "tasks"
export class SupaBaseDatabase {
static getInstance() {
return new SupaBaseDatabase();
private client: SupabaseClient;

constructor(client: SupabaseClient) {
this.client = client;
}

static getInstance(): SupaBaseDatabase {
if (!supabase) {
throw new Error('Supabase has not been initialized because SUPABASE_URL is not defined');
}
return new SupaBaseDatabase(supabase);
}

async fetchData() {
const { data, error } = await supabase.from(DB_NAME).select("*");
const { data, error } = await this.client.from(DB_NAME).select("*");

if (error) {
console.error("Error fetching tasks:", error);
Expand All @@ -27,9 +50,15 @@ export class SupaBaseDatabase {
}
}
async insertData(payload: any) {
if (!this.client) {
throw new Error('Supabase client is not initialized');
}

payload.question = payload.question.toLowerCase();
payload.author_name = payload.author_name.toLowerCase();
const { data, error } = await supabase.from(DB_NAME).insert([payload]);

const { data, error } = await this.client.from(DB_NAME).insert([payload]);

if (error) {
console.error("Error inserting Q&A:", error);
} else {
Expand All @@ -38,7 +67,7 @@ export class SupaBaseDatabase {
}
async addFeedback(payload: FeedbackPayload) {
const { answerQuality, feedbackId, rating, timestamp } = payload;
const { data, error, status } = await supabase
const { data, error, status } = await this.client
.from(DB_NAME)
.update({
answerQuality,
Expand All @@ -58,7 +87,7 @@ export class SupaBaseDatabase {
async getAnswerByQuestion(question: string, author?: string) {
const oneDayBefore = new Date();
oneDayBefore.setDate(oneDayBefore.getDate() - 1);
let query = supabase
let query = this.client
.from(DB_NAME)
.select("answer, createdAt")
.eq('question', question);
Expand All @@ -83,3 +112,56 @@ export class SupaBaseDatabase {
}
}
}

export const getCachedAnswer = async (question: string, author?: string) => {
question = question.toLowerCase();
author = author?.toLowerCase();
const answers = await SupaBaseDatabase.getInstance().getAnswerByQuestion(
question,
author
);

if (!answers || answers.length === 0) {
console.error("Error fetching answer: No answers found.");
return null;
}

// Use JavaScript .find() method to get first element where answer is not an empty string
const nonEmptyAnswer = answers.find((item) => item.answer.trim() !== "");

if (!nonEmptyAnswer) {
console.error("Error fetching answer: No non-empty answers found.");
return null;
}

// Return the nonEmptyAnswer directly as a string
return createReadableStream(nonEmptyAnswer.answer);
};

export const addDocumentToSupabase = async (payload: any) => {
await SupaBaseDatabase.getInstance().insertData(payload);
};

function createReadableStream(text: string) {
const encoder = new TextEncoder();
const readable = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode(text));
controller.close();
},
});
return readable;
}

export const addFeedback = async (feedback: FeedbackPayload, feedbackId: string) => {
const { status, error } = await SupaBaseDatabase.getInstance().addFeedback({
...feedback,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to spread the feedback object. Pass it directly.

Suggested change
...feedback,
feedback,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get this from my linter when I change to your suggestion:

Argument of type '{ feedback: FeedbackPayload; feedbackId: string; }' is not assignable to parameter of type 'FeedbackPayload'. Object literal may only specify known properties, but 'feedback' does not exist in type 'FeedbackPayload'. Did you mean to write 'feedbackId'?ts(2345)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. its a type issue but this is fine too

feedbackId,
});

if (status >= 200 && status < 300 && !error) {
console.log("Feedback sent successfully");
return true;
}
return false;
};
64 changes: 18 additions & 46 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { isMobile } from "react-device-detect";
import MessageBox, { Message } from "@/components/message/message";
import { defaultErrorMessage } from "@/config/error-config";
import { v4 as uuidv4 } from "uuid";
import { SupaBaseDatabase } from "@/database/database";
import { isSupabaseInitialized, getCachedAnswer, addDocumentToSupabase } from "@/database/database";
import BackgroundHelper from "@/components/background/BackgroundHelper";
import Rating from "@/components/rating/Rating";
import { useRouter } from "next/router";
Expand All @@ -36,42 +36,6 @@ interface FeedbackStatus {
[messageId: string]: "submitted" | undefined;
}

function createReadableStream(text: string) {
const encoder = new TextEncoder();
const readable = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode(text));
controller.close();
},
});
return readable;
}

const getCachedAnswer = async (question: string, author?: string) => {
question = question.toLowerCase();
author = author?.toLocaleLowerCase();
const answers = await SupaBaseDatabase.getInstance().getAnswerByQuestion(
question,
author
);

if (!answers || answers.length === 0) {
console.error("Error fetching answer: No answers found.");
return null;
}

// Use JavaScript .find() method to get first element where answer is not an empty string
const nonEmptyAnswer = answers.find((item) => item.answer.trim() !== "");

if (!nonEmptyAnswer) {
console.error("Error fetching answer: No non-empty answers found.");
return null;
}

// Return the nonEmptyAnswer directly as a string
return createReadableStream(nonEmptyAnswer.answer);
};

function formatDate(date: Date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0"); // Months are 0-based, so we need to add 1
Expand Down Expand Up @@ -260,16 +224,24 @@ export default function Home() {
const errMessage = "Something went wrong. Try again later";

try {
const cachedAnswer = await getCachedAnswer(query, author);
let data = null;
if (!cachedAnswer) {
let data;
if (isSupabaseInitialized) {
const cachedAnswer = await getCachedAnswer(query, author);
if (!cachedAnswer) {
const response: Response = await fetchResult(query, author);
if (!response.ok) {
throw new Error(errMessage);
}
data = response.body;
} else {
data = cachedAnswer;
}
} else {
const response: Response = await fetchResult(query, author);
if (!response.ok) {
throw new Error(errMessage);
}
data = response.body;
} else {
data = cachedAnswer;
}

const reader = data?.getReader();
Expand Down Expand Up @@ -308,7 +280,7 @@ export default function Home() {
const dateObject = new Date(dateTimeString);
const formattedDateTime = formatDate(dateObject);

if (!errorMessages.includes(answer)) {
if (isSupabaseInitialized && !errorMessages.includes(answer)) {
let payload = {
uniqueId: uniqueIDD,
question: question,
Expand All @@ -319,8 +291,8 @@ export default function Home() {
updatedAt: null,
releasedAt: formattedDateTime,
};
await SupaBaseDatabase.getInstance().insertData(payload);
} else {
addDocumentToSupabase(payload);
} else if (isSupabaseInitialized) {
// If answer contains error messages, only add the question to DB
let payload = {
uniqueId: uniqueIDD,
Expand All @@ -332,7 +304,7 @@ export default function Home() {
updatedAt: null,
releasedAt: formattedDateTime,
};
await SupaBaseDatabase.getInstance().insertData(payload);
addDocumentToSupabase(payload);
}
await updateMessages(finalAnswerWithLinks, uuid);
} catch (err: any) {
Expand Down