mirror of
https://github.com/coaidev/coai.git
synced 2025-06-08 06:40:32 +09:00
update subscription usage form
This commit is contained in:
parent
65eb601d84
commit
74be9cb54d
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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";
|
||||||
|
@ -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 } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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`}>
|
||||||
|
@ -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
|
||||||
|
@ -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),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
17
auth/user.go
17
auth/user.go
@ -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
14
globals/quota.go
Normal 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)
|
||||||
|
}
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user