update subscription usage form

This commit is contained in:
Zhang Minghan 2023-10-25 23:48:41 +08:00
parent 65eb601d84
commit 74be9cb54d
10 changed files with 112 additions and 33 deletions

View File

@ -6,23 +6,51 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: 18px 4px !important; margin: 18px 4px !important;
margin-bottom: 32px !important; margin-bottom: 48px !important;
align-items: center; align-items: center;
} }
.date { .sub-row {
display: flex; display: flex;
flex-direction: row; flex-direction: column;
align-items: center;
color: hsl(45, 100%, 50%);
padding: 8px 16px; padding: 8px 16px;
margin-top: 16px; margin-top: 16px;
margin-bottom: 8px;
border-radius: var(--radius); border-radius: var(--radius);
border: 1px solid hsl(var(--border)); border: 1px solid hsl(var(--border));
align-items: center;
svg { .sub-column {
flex-shrink: 0; display: flex;
transform: translateY(1px); 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;
}
} }
} }

View File

@ -1,7 +1,7 @@
import axios from "axios"; import axios from "axios";
import { Model } from "./conversation/types.ts"; 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 dev: boolean = window.location.hostname === "localhost";
export const deploy: boolean = true; export const deploy: boolean = true;
export let rest_api: string = "http://localhost:8094"; export let rest_api: string = "http://localhost:8094";

View File

@ -15,6 +15,10 @@ type SubscriptionResponse = {
status: boolean; status: boolean;
is_subscribed: boolean; is_subscribed: boolean;
expired: number; expired: number;
usage: {
gpt4: number;
dalle: number;
}
}; };
type BuySubscriptionResponse = { type BuySubscriptionResponse = {
@ -58,16 +62,12 @@ export async function getSubscription(): Promise<SubscriptionResponse> {
try { try {
const resp = await axios.get(`/subscription`); const resp = await axios.get(`/subscription`);
if (resp.data.status === false) { 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 { return resp.data as SubscriptionResponse;
status: resp.data.status,
is_subscribed: resp.data.is_subscribed,
expired: resp.data.expired,
};
} catch (e) { } catch (e) {
console.debug(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 } };
} }
} }

View File

@ -4,7 +4,7 @@ import {
isSubscribedSelector, isSubscribedSelector,
refreshSubscription, refreshSubscription,
refreshSubscriptionTask, refreshSubscriptionTask,
setDialog, setDialog, usageSelector,
} from "../store/subscription.ts"; } from "../store/subscription.ts";
import { import {
Dialog, Dialog,
@ -158,6 +158,7 @@ function Subscription() {
const open = useSelector(dialogSelector); const open = useSelector(dialogSelector);
const subscription = useSelector(isSubscribedSelector); const subscription = useSelector(isSubscribedSelector);
const expired = useSelector(expiredSelector); const expired = useSelector(expiredSelector);
const usage = useSelector(usageSelector);
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
refreshSubscriptionTask(dispatch); refreshSubscriptionTask(dispatch);
@ -174,9 +175,27 @@ function Subscription() {
<DialogDescription asChild> <DialogDescription asChild>
<div className={`sub-wrapper`}> <div className={`sub-wrapper`}>
{subscription && ( {subscription && (
<div className={`date`}> <div className={`sub-row`}>
<Calendar className={`h-4 w-4 mr-1`} /> <div className={`sub-column`}>
{t("sub.expired", { expired })} <Calendar className={`h-4 w-4 mr-1`} />
{t("sub.expired", { expired })}
</div>
<div className={`sub-column`}>
<Compass className={`h-4 w-4 mr-1`} />
GPT-4
<div className={`grow`} />
<div className={`sub-value`}>
<p>{ usage.gpt4 }</p> / <p> 50 </p>
</div>
</div>
<div className={`sub-column`}>
<ImagePlus className={`h-4 w-4 mr-1`} />
DALL-E
<div className={`grow`} />
<div className={`sub-value`}>
<p>{ usage.dalle }</p> / <p> 2000 </p>
</div>
</div>
</div> </div>
)} )}
<div className={`plan-wrapper`}> <div className={`plan-wrapper`}>

View File

@ -8,6 +8,10 @@ export const subscriptionSlice = createSlice({
dialog: false, dialog: false,
is_subscribed: false, is_subscribed: false,
expired: 0, expired: 0,
usage: {
gpt4: 0,
dalle: 0,
}
}, },
reducers: { reducers: {
toggleDialog: (state) => { toggleDialog: (state) => {
@ -25,6 +29,7 @@ export const subscriptionSlice = createSlice({
updateSubscription: (state, action) => { updateSubscription: (state, action) => {
state.is_subscribed = action.payload.is_subscribed; state.is_subscribed = action.payload.is_subscribed;
state.expired = action.payload.expired; state.expired = action.payload.expired;
state.usage = action.payload.usage;
}, },
}, },
}); });
@ -44,6 +49,8 @@ export const isSubscribedSelector = (state: any): boolean =>
state.subscription.is_subscribed; state.subscription.is_subscribed;
export const expiredSelector = (state: any): number => export const expiredSelector = (state: any): number =>
state.subscription.expired; state.subscription.expired;
export const usageSelector = (state: any): any =>
state.subscription.usage;
export const refreshSubscription = async (dispatch: AppDispatch) => { export const refreshSubscription = async (dispatch: AppDispatch) => {
const current = new Date().getTime(); //@ts-ignore const current = new Date().getTime(); //@ts-ignore

View File

@ -62,10 +62,12 @@ func SubscriptionAPI(c *gin.Context) {
} }
db := utils.GetDBFromContext(c) db := utils.GetDBFromContext(c)
cache := utils.GetCacheFromContext(c)
c.JSON(200, gin.H{ c.JSON(200, gin.H{
"status": true, "status": true,
"is_subscribed": user.IsSubscribe(db), "is_subscribed": user.IsSubscribe(db),
"expired": user.GetSubscriptionExpiredDay(db), "expired": user.GetSubscriptionExpiredDay(db),
"usage": user.GetSubscriptionUsage(db, cache),
}) })
} }

View File

@ -1,11 +1,10 @@
package auth package auth
import ( import (
"chat/globals"
"chat/utils" "chat/utils"
"database/sql" "database/sql"
"fmt"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"time"
) )
func CountSubscriptionPrize(month int) float32 { 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 { func IncreaseSubscriptionUsage(cache *redis.Client, user *User) bool {
today := time.Now().Format("2006-01-02") return utils.IncrWithLimit(cache, globals.GetGPT4LimitFormat(user.ID), 1, 50, 60*60*24) // 1 day
return utils.IncrWithLimit(cache, fmt.Sprintf(":subscription-usage:%s:%d", today, user.ID), 1, 50, 60*60*24) // 1 day
} }
func DecreaseSubscriptionUsage(cache *redis.Client, user *User) bool { func DecreaseSubscriptionUsage(cache *redis.Client, user *User) bool {
today := time.Now().Format("2006-01-02") return utils.DecrInt(cache, globals.GetGPT4LimitFormat(user.ID), 1)
return utils.DecrInt(cache, fmt.Sprintf(":subscription-usage:%s:%d", today, user.ID), 1)
} }
func CanEnableSubscription(db *sql.DB, cache *redis.Client, user *User) bool { func CanEnableSubscription(db *sql.DB, cache *redis.Client, user *User) bool {

View File

@ -1,12 +1,14 @@
package auth package auth
import ( import (
"chat/globals"
"chat/utils" "chat/utils"
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"github.com/spf13/viper" "github.com/spf13/viper"
"math" "math"
"net/http" "net/http"
@ -162,6 +164,21 @@ func (u *User) GetSubscriptionExpiredDay(db *sql.DB) int {
return int(math.Round(stamp.Hours() / 24)) 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 { func (u *User) AddSubscription(db *sql.DB, month int) bool {
current := u.GetSubscription(db) current := u.GetSubscription(db)
if current.Unix() < time.Now().Unix() { if current.Unix() < time.Now().Unix() {

14
globals/quota.go Normal file
View File

@ -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)
}

View File

@ -3,18 +3,13 @@ package manager
import ( import (
"chat/adapter/chatgpt" "chat/adapter/chatgpt"
"chat/auth" "chat/auth"
"chat/globals"
"chat/utils" "chat/utils"
"database/sql"
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"strings" "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) { func GenerateImage(c *gin.Context, user *auth.User, prompt string) (string, error) {
// free plan: 5 images per day // free plan: 5 images per day
// pro plan: 50 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) db := utils.GetDBFromContext(c)
cache := utils.GetCacheFromContext(c) cache := utils.GetCacheFromContext(c)
key := GetImageLimitFormat(db, user) key := globals.GetImageLimitFormat(user.GetID(db))
usage := auth.GetDalleUsageLimit(db, user) usage := auth.GetDalleUsageLimit(db, user)
prompt = strings.TrimSpace(prompt) prompt = strings.TrimSpace(prompt)