mirror of
https://github.com/StanGirard/quivr.git
synced 2024-12-23 19:32:30 +03:00
feat: 🎸 models (#1967)
updated available models # Description Please include a summary of the changes and the related issue. Please also include relevant motivation and context. ## Checklist before requesting a review Please delete options that are not relevant. - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented hard-to-understand areas - [ ] I have ideally added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged ## Screenshots (if appropriate):
This commit is contained in:
parent
fae71f1935
commit
a65eb5a9cd
1
.gitignore
vendored
1
.gitignore
vendored
@ -73,3 +73,4 @@ backend/slim.report.json
|
||||
volumes/db/data/
|
||||
volumes/storage/stub/stub/quivr/*
|
||||
supabase/migrations/20240103191539_private.sql
|
||||
supabase/20240103191539_private.sql
|
||||
|
@ -48,6 +48,7 @@ You can find the installation video [here](https://www.youtube.com/watch?v=cXBa6
|
||||
|
||||
```bash
|
||||
git clone https://github.com/StanGirard/Quivr.git && cd Quivr
|
||||
git checkout v0.0.152
|
||||
```
|
||||
|
||||
- **Step 2**: Copy the `.env.example` files
|
||||
|
@ -1,4 +1,3 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
@ -12,7 +11,9 @@ class UserUsage(Repository):
|
||||
def __init__(self, supabase_client):
|
||||
self.db = supabase_client
|
||||
|
||||
def create_user_daily_usage(self, user_id: UUID, user_email: str, date: datetime):
|
||||
def create_user_daily_usage(
|
||||
self, user_id: UUID, user_email: str, date: datetime, number: int = 1
|
||||
):
|
||||
return (
|
||||
self.db.table("user_daily_usage")
|
||||
.insert(
|
||||
@ -20,29 +21,37 @@ class UserUsage(Repository):
|
||||
"user_id": str(user_id),
|
||||
"email": user_email,
|
||||
"date": date,
|
||||
"daily_requests_count": 1,
|
||||
"daily_requests_count": number,
|
||||
}
|
||||
)
|
||||
.execute()
|
||||
)
|
||||
|
||||
def check_if_is_premium_user(self, user_id: UUID):
|
||||
def check_subscription_validity(self, customer_id: str) -> bool:
|
||||
"""
|
||||
Check if the user is a premium user
|
||||
Check if the subscription of the user is still valid
|
||||
"""
|
||||
matching_customers = None
|
||||
try:
|
||||
is_premium_user = (
|
||||
self.db.from_("user_settings")
|
||||
.select("is_premium")
|
||||
.filter("user_id", "eq", str(user_id))
|
||||
.execute()
|
||||
.data
|
||||
)
|
||||
now = datetime.now()
|
||||
|
||||
if len(is_premium_user) > 0 and is_premium_user[0]["is_premium"]:
|
||||
# Format the datetime object as a string in the appropriate format for your Supabase database
|
||||
now_str = now.strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
subscription_still_valid = (
|
||||
self.db.from_("subscriptions")
|
||||
.select("*")
|
||||
.filter(
|
||||
"customer", "eq", customer_id
|
||||
) # then check if current_period_end is greater than now with timestamp format
|
||||
.filter("current_period_end", "gt", now_str)
|
||||
.execute()
|
||||
).data
|
||||
|
||||
if len(subscription_still_valid) > 0:
|
||||
return True
|
||||
|
||||
def check_user_is_customer(self, user_id: UUID) -> (bool, str):
|
||||
"""
|
||||
Check if the user is a customer and return the customer id
|
||||
"""
|
||||
user_email_customer = (
|
||||
self.db.from_("users")
|
||||
.select("*")
|
||||
@ -51,7 +60,7 @@ class UserUsage(Repository):
|
||||
).data
|
||||
|
||||
if len(user_email_customer) == 0:
|
||||
return False
|
||||
return False, None
|
||||
|
||||
matching_customers = (
|
||||
self.db.table("customers")
|
||||
@ -61,27 +70,89 @@ class UserUsage(Repository):
|
||||
).data
|
||||
|
||||
if len(matching_customers) == 0:
|
||||
return False
|
||||
return False, None
|
||||
|
||||
now = datetime.now()
|
||||
return True, matching_customers[0]["id"]
|
||||
|
||||
# Format the datetime object as a string in the appropriate format for your Supabase database
|
||||
now_str = now.strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
subscription_still_valid = (
|
||||
self.db.from_("subscriptions")
|
||||
.select("*")
|
||||
.filter(
|
||||
"customer", "eq", matching_customers[0]["id"]
|
||||
) # then check if current_period_end is greater than now with timestamp format
|
||||
.filter("current_period_end", "gt", now_str)
|
||||
def update_customer_settings_with_product_settings(
|
||||
self, user_id: UUID, customer_id: str
|
||||
):
|
||||
"""
|
||||
Check if the user is a customer and return the customer id
|
||||
"""
|
||||
|
||||
matching_products = (
|
||||
self.db.table("subscriptions")
|
||||
.select("attrs")
|
||||
.filter("customer", "eq", customer_id)
|
||||
.execute()
|
||||
).data
|
||||
|
||||
if len(subscription_still_valid) > 0:
|
||||
if len(matching_customers) > 0:
|
||||
# Output object
|
||||
# {"id":"sub_1OUZOgJglvQxkJ1H98TSY9bv","plan":{"id":"price_1NwMsXJglvQxkJ1Hbzs5JkTs","active":true,"amount":1900,"object":"plan","created":1696156081,"product":"prod_OjqZPhbBQwmsB8","currency":"usd","interval":"month","livemode":false,"metadata":{},"nickname":null,"tiers_mode":null,"usage_type":"licensed","amount_decimal":"1900","billing_scheme":"per_unit","interval_count":1,"aggregate_usage":null,"transform_usage":null,"trial_period_days":null},"items":{"url":"/v1/subscription_items?subscription=sub_1OUZOgJglvQxkJ1H98TSY9bv","data":[{"id":"si_PJBm1ciQlpaOA4","plan":{"id":"price_1NwMsXJglvQxkJ1Hbzs5JkTs","active":true,"amount":1900,"object":"plan","created":1696156081,"product":"prod_OjqZPhbBQwmsB8","currency":"usd","interval":"month","livemode":false,"metadata":{},"nickname":null,"tiers_mode":null,"usage_type":"licensed","amount_decimal":"1900","billing_scheme":"per_unit","interval_count":1,"aggregate_usage":null,"transform_usage":null,"trial_period_days":null},"price":{"id":"price_1NwMsXJglvQxkJ1Hbzs5JkTs","type":"recurring","active":true,"object":"price","created":1696156081,"product":"prod_OjqZPhbBQwmsB8","currency":"usd","livemode":false,"metadata":{},"nickname":null,"recurring":{"interval":"month","usage_type":"licensed","interval_count":1,"aggregate_usage":null,"trial_period_days":null},"lookup_key":null,"tiers_mode":null,"unit_amount":1900,"tax_behavior":"unspecified","billing_scheme":"per_unit","custom_unit_amount":null,"transform_quantity":null,"unit_amount_decimal":"1900"},"object":"subscription_item","created":1704307355,"metadata":{},"quantity":1,"tax_rates":[],"subscription":"sub_1OUZOgJglvQxkJ1H98TSY9bv","billing_thresholds":null}],"object":"list","has_more":false,"total_count":1},"object":"subscription","status":"active","created":1704307354,"currency":"usd","customer":"cus_PJBmxGOKfQgYDN","discount":null,"ended_at":null,"livemode":false,"metadata":{},"quantity":1,"schedule":null,"cancel_at":null,"trial_end":null,"start_date":1704307354,"test_clock":null,"application":null,"canceled_at":null,"description":null,"trial_start":null,"on_behalf_of":null,"automatic_tax":{"enabled":true},"transfer_data":null,"days_until_due":null,"default_source":null,"latest_invoice":"in_1OUZOgJglvQxkJ1HysujPh0b","pending_update":null,"trial_settings":{"end_behavior":{"missing_payment_method":"create_invoice"}},"pause_collection":null,"payment_settings":{"payment_method_types":null,"payment_method_options":null,"save_default_payment_method":"off"},"collection_method":"charge_automatically","default_tax_rates":[],"billing_thresholds":null,"current_period_end":1706985754,"billing_cycle_anchor":1704307354,"cancel_at_period_end":false,"cancellation_details":{"reason":null,"comment":null,"feedback":null},"current_period_start":1704307354,"pending_setup_intent":null,"default_payment_method":"pm_1OUZOfJglvQxkJ1HSHU0TTWW","application_fee_percent":null,"pending_invoice_item_interval":null,"next_pending_invoice_item_invoice":null}
|
||||
|
||||
# Now extract the product id from the object
|
||||
|
||||
if len(matching_products) == 0:
|
||||
logger.info("No matching products found")
|
||||
return
|
||||
|
||||
product_id = matching_products[0]["attrs"]["items"]["data"][0]["plan"][
|
||||
"product"
|
||||
]
|
||||
|
||||
# Now fetch the product settings
|
||||
|
||||
matching_product_settings = (
|
||||
self.db.table("product_to_features")
|
||||
.select("*")
|
||||
.filter("stripe_product_id", "eq", product_id)
|
||||
.execute()
|
||||
).data
|
||||
|
||||
if len(matching_product_settings) == 0:
|
||||
logger.info("No matching product settings found")
|
||||
return
|
||||
|
||||
product_settings = matching_product_settings[0]
|
||||
|
||||
# Now update the user settings with the product settings
|
||||
try:
|
||||
self.db.table("user_settings").update(
|
||||
{
|
||||
"max_brains": product_settings["max_brains"],
|
||||
"max_brain_size": product_settings["max_brain_size"],
|
||||
"daily_chat_credit": product_settings["daily_chat_credit"],
|
||||
"api_access": product_settings["api_access"],
|
||||
"models": product_settings["models"],
|
||||
}
|
||||
).match({"user_id": str(user_id)}).execute()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
logger.error("Error while updating user settings with product settings")
|
||||
|
||||
def check_if_is_premium_user(self, user_id: UUID):
|
||||
"""
|
||||
Check if the user is a premium user
|
||||
"""
|
||||
matching_customers = None
|
||||
try:
|
||||
user_is_customer, user_customer_id = self.check_user_is_customer(user_id)
|
||||
logger.info("🔥🔥🔥")
|
||||
logger.info(user_is_customer)
|
||||
logger.info(user_customer_id)
|
||||
|
||||
if user_is_customer:
|
||||
self.db.table("user_settings").update({"is_premium": True}).match(
|
||||
{"user_id": str(user_id)}
|
||||
).execute()
|
||||
|
||||
if user_is_customer and self.check_subscription_validity(user_customer_id):
|
||||
logger.info("User is a premium user")
|
||||
self.update_customer_settings_with_product_settings(
|
||||
user_id, user_customer_id
|
||||
)
|
||||
return True
|
||||
else:
|
||||
self.db.table("user_settings").update({"is_premium": False}).match(
|
||||
@ -91,6 +162,7 @@ class UserUsage(Repository):
|
||||
|
||||
except Exception as e:
|
||||
logger.info(matching_customers)
|
||||
logger.error(e)
|
||||
logger.error(
|
||||
"Error while checking if user is a premium user. Stripe needs to be configured."
|
||||
)
|
||||
@ -125,18 +197,29 @@ class UserUsage(Repository):
|
||||
check_is_premium = self.check_if_is_premium_user(user_id)
|
||||
|
||||
if check_is_premium:
|
||||
user_settings["max_brains"] = int(
|
||||
os.environ.get("PREMIUM_MAX_BRAIN_NUMBER", 12)
|
||||
)
|
||||
user_settings["max_brain_size"] = int(
|
||||
os.environ.get("PREMIUM_MAX_BRAIN_SIZE", 50000000)
|
||||
)
|
||||
user_settings["daily_chat_credit"] = int(
|
||||
os.environ.get("PREMIUM_DAILY_CHAT_CREDIT", 100)
|
||||
)
|
||||
# get the possibly updated user settings
|
||||
user_settings_response = (
|
||||
self.db.from_("user_settings")
|
||||
.select("*")
|
||||
.filter("user_id", "eq", str(user_id))
|
||||
.execute()
|
||||
).data
|
||||
return user_settings_response[0]
|
||||
|
||||
return user_settings
|
||||
|
||||
def get_model_settings(self):
|
||||
"""
|
||||
Fetch the user settings from the database
|
||||
"""
|
||||
|
||||
model_settings_response = (self.db.from_("models").select("*").execute()).data
|
||||
|
||||
if len(model_settings_response) == 0:
|
||||
raise ValueError("An issue occured while fetching the model settings")
|
||||
|
||||
return model_settings_response
|
||||
|
||||
def get_user_usage(self, user_id):
|
||||
"""
|
||||
Fetch the user request stats from the database
|
||||
@ -165,13 +248,15 @@ class UserUsage(Repository):
|
||||
return response[0]["daily_requests_count"]
|
||||
return None
|
||||
|
||||
def increment_user_request_count(self, user_id, date, current_requests_count: int):
|
||||
def increment_user_request_count(
|
||||
self, user_id, date, current_requests_count: int, number: int = 1
|
||||
):
|
||||
"""
|
||||
Increment the user's requests count for a specific day
|
||||
"""
|
||||
|
||||
self.update_user_request_count(
|
||||
user_id, daily_requests_count=current_requests_count + 1, date=date
|
||||
user_id, daily_requests_count=current_requests_count + number, date=date
|
||||
)
|
||||
|
||||
def update_user_request_count(self, user_id, daily_requests_count, date):
|
||||
|
@ -24,6 +24,14 @@ class UserUsage(UserIdentity):
|
||||
|
||||
return request
|
||||
|
||||
def get_model_settings(self):
|
||||
"""
|
||||
Fetch the user request stats from the database
|
||||
"""
|
||||
request = self.supabase_db.get_model_settings()
|
||||
|
||||
return request
|
||||
|
||||
def get_user_settings(self):
|
||||
"""
|
||||
Fetch the user settings from the database
|
||||
@ -32,7 +40,7 @@ class UserUsage(UserIdentity):
|
||||
|
||||
return request
|
||||
|
||||
def handle_increment_user_request_count(self, date):
|
||||
def handle_increment_user_request_count(self, date, number=1):
|
||||
"""
|
||||
Increment the user request count in the database
|
||||
"""
|
||||
@ -44,15 +52,16 @@ class UserUsage(UserIdentity):
|
||||
if self.email is None:
|
||||
raise ValueError("User Email should be defined for daily usage table")
|
||||
self.supabase_db.create_user_daily_usage(
|
||||
user_id=self.id, date=date, user_email=self.email
|
||||
user_id=self.id, date=date, user_email=self.email, number=number
|
||||
)
|
||||
self.daily_requests_count = 1
|
||||
self.daily_requests_count = number
|
||||
return
|
||||
|
||||
self.supabase_db.increment_user_request_count(
|
||||
user_id=self.id,
|
||||
date=date,
|
||||
current_requests_count=current_requests_count,
|
||||
number=number,
|
||||
)
|
||||
|
||||
self.daily_requests_count = current_requests_count
|
||||
|
@ -21,21 +21,27 @@ class NullableUUID(UUID):
|
||||
return None
|
||||
|
||||
|
||||
def check_user_requests_limit(
|
||||
user: UserIdentity,
|
||||
):
|
||||
def check_user_requests_limit(user: UserIdentity, model: str):
|
||||
userDailyUsage = UserUsage(id=user.id, email=user.email)
|
||||
|
||||
userSettings = userDailyUsage.get_user_settings()
|
||||
|
||||
date = time.strftime("%Y%m%d")
|
||||
userDailyUsage.handle_increment_user_request_count(date)
|
||||
|
||||
daily_chat_credit = userSettings.get("daily_chat_credit", 0)
|
||||
models_price = userDailyUsage.get_model_settings()
|
||||
user_choosen_model_price = 1000
|
||||
|
||||
for model_setting in models_price:
|
||||
if model_setting["name"] == model:
|
||||
user_choosen_model_price = model_setting["price"]
|
||||
if int(userDailyUsage.daily_requests_count) >= int(daily_chat_credit):
|
||||
raise HTTPException(
|
||||
status_code=429, # pyright: ignore reportPrivateUsage=none
|
||||
detail=f"You have reached your daily chat limit of {daily_chat_credit} requests per day. Please upgrade your plan to increase your daily chat limit.",
|
||||
)
|
||||
else:
|
||||
userDailyUsage.handle_increment_user_request_count(
|
||||
date, user_choosen_model_price
|
||||
)
|
||||
pass
|
||||
|
@ -155,7 +155,7 @@ async def create_question_handler(
|
||||
chat_question.max_tokens = chat_question.max_tokens or fallback_max_tokens
|
||||
|
||||
try:
|
||||
check_user_requests_limit(current_user)
|
||||
check_user_requests_limit(current_user, chat_question.model)
|
||||
is_model_ok = (chat_question).model in user_settings.get("models", ["gpt-3.5-turbo"]) # type: ignore
|
||||
gpt_answer_generator = chat_instance.get_answer_generator(
|
||||
chat_id=str(chat_id),
|
||||
@ -229,7 +229,7 @@ async def create_stream_question_handler(
|
||||
|
||||
try:
|
||||
logger.info(f"Streaming request for {chat_question.model}")
|
||||
check_user_requests_limit(current_user)
|
||||
check_user_requests_limit(current_user, chat_question.model)
|
||||
# TODO check if model is in the list of models available for the user
|
||||
|
||||
is_model_ok = chat_question.model in user_settings.get("models", ["gpt-3.5-turbo"]) # type: ignore
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { useFeatureIsOn } from "@growthbook/growthbook-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { StripePricingModal } from "@/lib/components/Stripe";
|
||||
@ -10,11 +9,6 @@ const MANAGE_PLAN_URL = process.env.NEXT_PUBLIC_STRIPE_MANAGE_PLAN_URL;
|
||||
export const StripePricingOrManageButton = (): JSX.Element => {
|
||||
const { t } = useTranslation("monetization");
|
||||
const { userData } = useUserData();
|
||||
const monetizationIsOn = useFeatureIsOn("monetization");
|
||||
|
||||
if (!monetizationIsOn) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const is_premium = userData?.is_premium ?? false;
|
||||
if (is_premium) {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { useFeatureIsOn } from "@growthbook/growthbook-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { LuStar } from "react-icons/lu";
|
||||
|
||||
@ -9,10 +8,9 @@ import { useUserData } from "@/lib/hooks/useUserData";
|
||||
export const UpgradeToPlus = (): JSX.Element => {
|
||||
const { userData } = useUserData();
|
||||
const is_premium = userData?.is_premium;
|
||||
const featureIsOn = useFeatureIsOn("monetization");
|
||||
const { t } = useTranslation("monetization");
|
||||
|
||||
if (!featureIsOn || is_premium === true) {
|
||||
if (is_premium === true) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { useFeatureIsOn } from "@growthbook/growthbook-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FiUser } from "react-icons/fi";
|
||||
|
||||
@ -10,10 +9,9 @@ import { SidebarFooterButton } from "./SidebarFooterButton";
|
||||
export const UpgradeToPlus = (): JSX.Element => {
|
||||
const { userData } = useUserData();
|
||||
const is_premium = userData?.is_premium;
|
||||
const featureIsOn = useFeatureIsOn("monetization");
|
||||
const { t } = useTranslation("monetization");
|
||||
|
||||
if (!featureIsOn || is_premium === true) {
|
||||
if (is_premium === true) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
|
3
supabase/migrations/20240103231656_product.sql
Normal file
3
supabase/migrations/20240103231656_product.sql
Normal file
@ -0,0 +1,3 @@
|
||||
alter table "public"."product_to_features" alter column "models" set not null;
|
||||
|
||||
|
55
supabase/migrations/20240103234423_models.sql
Normal file
55
supabase/migrations/20240103234423_models.sql
Normal file
@ -0,0 +1,55 @@
|
||||
create table "public"."models" (
|
||||
"name" text not null,
|
||||
"price" integer default 1,
|
||||
"max_input" integer default 2000,
|
||||
"max_output" integer default 1000
|
||||
);
|
||||
|
||||
|
||||
CREATE UNIQUE INDEX models_pkey ON public.models USING btree (name);
|
||||
|
||||
alter table "public"."models" add constraint "models_pkey" PRIMARY KEY using index "models_pkey";
|
||||
|
||||
grant delete on table "public"."models" to "anon";
|
||||
|
||||
grant insert on table "public"."models" to "anon";
|
||||
|
||||
grant references on table "public"."models" to "anon";
|
||||
|
||||
grant select on table "public"."models" to "anon";
|
||||
|
||||
grant trigger on table "public"."models" to "anon";
|
||||
|
||||
grant truncate on table "public"."models" to "anon";
|
||||
|
||||
grant update on table "public"."models" to "anon";
|
||||
|
||||
grant delete on table "public"."models" to "authenticated";
|
||||
|
||||
grant insert on table "public"."models" to "authenticated";
|
||||
|
||||
grant references on table "public"."models" to "authenticated";
|
||||
|
||||
grant select on table "public"."models" to "authenticated";
|
||||
|
||||
grant trigger on table "public"."models" to "authenticated";
|
||||
|
||||
grant truncate on table "public"."models" to "authenticated";
|
||||
|
||||
grant update on table "public"."models" to "authenticated";
|
||||
|
||||
grant delete on table "public"."models" to "service_role";
|
||||
|
||||
grant insert on table "public"."models" to "service_role";
|
||||
|
||||
grant references on table "public"."models" to "service_role";
|
||||
|
||||
grant select on table "public"."models" to "service_role";
|
||||
|
||||
grant trigger on table "public"."models" to "service_role";
|
||||
|
||||
grant truncate on table "public"."models" to "service_role";
|
||||
|
||||
grant update on table "public"."models" to "service_role";
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user