diff --git a/app/src/assets/subscription.less b/app/src/assets/subscription.less index 660eb02..464c7a1 100644 --- a/app/src/assets/subscription.less +++ b/app/src/assets/subscription.less @@ -6,23 +6,51 @@ display: flex; flex-direction: column; margin: 18px 4px !important; - margin-bottom: 32px !important; + margin-bottom: 48px !important; align-items: center; } -.date { +.sub-row { display: flex; - flex-direction: row; - align-items: center; - color: hsl(45, 100%, 50%); + flex-direction: column; padding: 8px 16px; margin-top: 16px; + margin-bottom: 8px; border-radius: var(--radius); border: 1px solid hsl(var(--border)); + align-items: center; - svg { - flex-shrink: 0; - transform: translateY(1px); + .sub-column { + display: flex; + flex-direction: row; + align-items: center; + color: hsl(45, 100%, 50%); + margin-bottom: 4px; + width: 100%; + + svg { + flex-shrink: 0; + transform: translateY(1px); + } + + .sub-value { + display: flex; + flex-direction: row; + align-items: center; + gap: 2px; + + p { + font-weight: bolder; + } + } + + &:first-child { + margin-bottom: 8px; + } + + &:last-child { + margin-bottom: 0; + } } } diff --git a/app/src/conf.ts b/app/src/conf.ts index 9a86663..b569a2e 100644 --- a/app/src/conf.ts +++ b/app/src/conf.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { Model } from "./conversation/types.ts"; -export const version = "3.5.12"; +export const version = "3.5.13"; export const dev: boolean = window.location.hostname === "localhost"; export const deploy: boolean = true; export let rest_api: string = "http://localhost:8094"; diff --git a/app/src/conversation/addition.ts b/app/src/conversation/addition.ts index a2b4afa..9a364f1 100644 --- a/app/src/conversation/addition.ts +++ b/app/src/conversation/addition.ts @@ -15,6 +15,10 @@ type SubscriptionResponse = { status: boolean; is_subscribed: boolean; expired: number; + usage: { + gpt4: number; + dalle: number; + } }; type BuySubscriptionResponse = { @@ -58,16 +62,12 @@ export async function getSubscription(): Promise { try { const resp = await axios.get(`/subscription`); if (resp.data.status === false) { - return { status: false, is_subscribed: false, expired: 0 }; + return { status: false, is_subscribed: false, expired: 0, usage: { gpt4: 0, dalle: 0 } }; } - return { - status: resp.data.status, - is_subscribed: resp.data.is_subscribed, - expired: resp.data.expired, - }; + return resp.data as SubscriptionResponse; } catch (e) { console.debug(e); - return { status: false, is_subscribed: false, expired: 0 }; + return { status: false, is_subscribed: false, expired: 0 , usage: { gpt4: 0, dalle: 0 } }; } } diff --git a/app/src/dialogs/Subscription.tsx b/app/src/dialogs/Subscription.tsx index 6383683..4a2093f 100644 --- a/app/src/dialogs/Subscription.tsx +++ b/app/src/dialogs/Subscription.tsx @@ -4,7 +4,7 @@ import { isSubscribedSelector, refreshSubscription, refreshSubscriptionTask, - setDialog, + setDialog, usageSelector, } from "../store/subscription.ts"; import { Dialog, @@ -158,6 +158,7 @@ function Subscription() { const open = useSelector(dialogSelector); const subscription = useSelector(isSubscribedSelector); const expired = useSelector(expiredSelector); + const usage = useSelector(usageSelector); const dispatch = useDispatch(); useEffect(() => { refreshSubscriptionTask(dispatch); @@ -174,9 +175,27 @@ function Subscription() {
{subscription && ( -
- - {t("sub.expired", { expired })} +
+
+ + {t("sub.expired", { expired })} +
+
+ + GPT-4 +
+
+

{ usage.gpt4 }

/

50

+
+
+
+ + DALL-E +
+
+

{ usage.dalle }

/

2000

+
+
)}
diff --git a/app/src/store/subscription.ts b/app/src/store/subscription.ts index 94a8bde..f12e157 100644 --- a/app/src/store/subscription.ts +++ b/app/src/store/subscription.ts @@ -8,6 +8,10 @@ export const subscriptionSlice = createSlice({ dialog: false, is_subscribed: false, expired: 0, + usage: { + gpt4: 0, + dalle: 0, + } }, reducers: { toggleDialog: (state) => { @@ -25,6 +29,7 @@ export const subscriptionSlice = createSlice({ updateSubscription: (state, action) => { state.is_subscribed = action.payload.is_subscribed; state.expired = action.payload.expired; + state.usage = action.payload.usage; }, }, }); @@ -44,6 +49,8 @@ export const isSubscribedSelector = (state: any): boolean => state.subscription.is_subscribed; export const expiredSelector = (state: any): number => state.subscription.expired; +export const usageSelector = (state: any): any => + state.subscription.usage; export const refreshSubscription = async (dispatch: AppDispatch) => { const current = new Date().getTime(); //@ts-ignore diff --git a/auth/controller.go b/auth/controller.go index bb17900..4a28a05 100644 --- a/auth/controller.go +++ b/auth/controller.go @@ -62,10 +62,12 @@ func SubscriptionAPI(c *gin.Context) { } db := utils.GetDBFromContext(c) + cache := utils.GetCacheFromContext(c) c.JSON(200, gin.H{ "status": true, "is_subscribed": user.IsSubscribe(db), "expired": user.GetSubscriptionExpiredDay(db), + "usage": user.GetSubscriptionUsage(db, cache), }) } diff --git a/auth/subscription.go b/auth/subscription.go index 334e940..653d6c7 100644 --- a/auth/subscription.go +++ b/auth/subscription.go @@ -1,11 +1,10 @@ package auth import ( + "chat/globals" "chat/utils" "database/sql" - "fmt" "github.com/go-redis/redis/v8" - "time" ) func CountSubscriptionPrize(month int) float32 { @@ -33,13 +32,11 @@ func BuySubscription(db *sql.DB, user *User, month int) bool { } func IncreaseSubscriptionUsage(cache *redis.Client, user *User) bool { - today := time.Now().Format("2006-01-02") - return utils.IncrWithLimit(cache, fmt.Sprintf(":subscription-usage:%s:%d", today, user.ID), 1, 50, 60*60*24) // 1 day + return utils.IncrWithLimit(cache, globals.GetGPT4LimitFormat(user.ID), 1, 50, 60*60*24) // 1 day } func DecreaseSubscriptionUsage(cache *redis.Client, user *User) bool { - today := time.Now().Format("2006-01-02") - return utils.DecrInt(cache, fmt.Sprintf(":subscription-usage:%s:%d", today, user.ID), 1) + return utils.DecrInt(cache, globals.GetGPT4LimitFormat(user.ID), 1) } func CanEnableSubscription(db *sql.DB, cache *redis.Client, user *User) bool { diff --git a/auth/user.go b/auth/user.go index 874a878..0b00723 100644 --- a/auth/user.go +++ b/auth/user.go @@ -1,12 +1,14 @@ package auth import ( + "chat/globals" "chat/utils" "database/sql" "errors" "fmt" "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" + "github.com/go-redis/redis/v8" "github.com/spf13/viper" "math" "net/http" @@ -162,6 +164,21 @@ func (u *User) GetSubscriptionExpiredDay(db *sql.DB) int { return int(math.Round(stamp.Hours() / 24)) } +type Usage struct { + GPT4 int64 `json:"gpt4"` + Dalle int64 `json:"dalle"` +} + +func (u *User) GetSubscriptionUsage(db *sql.DB, cache *redis.Client) Usage { + gpt4, _ := utils.GetInt(cache, globals.GetGPT4LimitFormat(u.GetID(db))) + dalle, _ := utils.GetInt(cache, globals.GetImageLimitFormat(u.GetID(db))) + + return Usage{ + GPT4: gpt4, + Dalle: dalle, + } +} + func (u *User) AddSubscription(db *sql.DB, month int) bool { current := u.GetSubscription(db) if current.Unix() < time.Now().Unix() { diff --git a/globals/quota.go b/globals/quota.go new file mode 100644 index 0000000..20618c7 --- /dev/null +++ b/globals/quota.go @@ -0,0 +1,14 @@ +package globals + +import ( + "fmt" + "time" +) + +func GetImageLimitFormat(id int64) string { + return fmt.Sprintf(":imagelimit:%s:%d", time.Now().Format("2006-01-02"), id) +} + +func GetGPT4LimitFormat(id int64) string { + return fmt.Sprintf(":subscription-usage:%s:%d", time.Now().Format("2006-01-02"), id) +} diff --git a/manager/image.go b/manager/image.go index c6e97fb..d88b255 100644 --- a/manager/image.go +++ b/manager/image.go @@ -3,18 +3,13 @@ package manager import ( "chat/adapter/chatgpt" "chat/auth" + "chat/globals" "chat/utils" - "database/sql" "fmt" "github.com/gin-gonic/gin" "strings" - "time" ) -func GetImageLimitFormat(db *sql.DB, user *auth.User) string { - return fmt.Sprintf(":imagelimit:%s:%d", time.Now().Format("2006-01-02"), user.GetID(db)) -} - func GenerateImage(c *gin.Context, user *auth.User, prompt string) (string, error) { // free plan: 5 images per day // pro plan: 50 images per day @@ -22,7 +17,7 @@ func GenerateImage(c *gin.Context, user *auth.User, prompt string) (string, erro db := utils.GetDBFromContext(c) cache := utils.GetCacheFromContext(c) - key := GetImageLimitFormat(db, user) + key := globals.GetImageLimitFormat(user.GetID(db)) usage := auth.GetDalleUsageLimit(db, user) prompt = strings.TrimSpace(prompt)