mirror of
https://github.com/coaidev/coai.git
synced 2025-05-25 16:00:15 +09:00
update package store
This commit is contained in:
parent
8d111a0a31
commit
f9aac47b67
@ -60,8 +60,9 @@ func TextChat(db *sql.DB, user *auth.User, conn *websocket.Conn, instance *conve
|
|||||||
SendSegmentMessage(conn, types.ChatGPTSegmentResponse{
|
SendSegmentMessage(conn, types.ChatGPTSegmentResponse{
|
||||||
Message: defaultErrorMessage,
|
Message: defaultErrorMessage,
|
||||||
Quota: buffer.GetQuota(),
|
Quota: buffer.GetQuota(),
|
||||||
End: false,
|
End: true,
|
||||||
})
|
})
|
||||||
|
return defaultErrorMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
// collect quota
|
// collect quota
|
||||||
|
@ -29,6 +29,7 @@ import {login, tokenField} from "./conf.ts";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Quota from "./routes/Quota.tsx";
|
import Quota from "./routes/Quota.tsx";
|
||||||
import {openDialog} from "./store/quota.ts";
|
import {openDialog} from "./store/quota.ts";
|
||||||
|
import Package from "./routes/Package.tsx";
|
||||||
|
|
||||||
function Settings() {
|
function Settings() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -113,6 +114,7 @@ function App() {
|
|||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<Quota />
|
<Quota />
|
||||||
|
<Package />
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
0
app/src/assets/package.less
Normal file
0
app/src/assets/package.less
Normal file
@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
.buy-button {
|
.buy-button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
transition: .25s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
34
app/src/conversation/addition.ts
Normal file
34
app/src/conversation/addition.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
type QuotaResponse = {
|
||||||
|
status: boolean;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PackageResponse = {
|
||||||
|
status: boolean;
|
||||||
|
cert: boolean;
|
||||||
|
teenager: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function buyQuota(
|
||||||
|
quota: number,
|
||||||
|
): Promise<QuotaResponse> {
|
||||||
|
try {
|
||||||
|
const resp = await axios.post(`/buy`, { quota });
|
||||||
|
return resp.data as QuotaResponse;
|
||||||
|
} catch (e) {
|
||||||
|
console.debug(e);
|
||||||
|
return { status: false, error: "network error" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPackage(): Promise<PackageResponse> {
|
||||||
|
try {
|
||||||
|
const resp = await axios.get(`/package`);
|
||||||
|
return resp.data as PackageResponse;
|
||||||
|
} catch (e) {
|
||||||
|
console.debug(e);
|
||||||
|
return { status: false, cert: false, teenager: false };
|
||||||
|
}
|
||||||
|
}
|
@ -66,6 +66,23 @@ const resources = {
|
|||||||
"output": "Output",
|
"output": "Output",
|
||||||
"tip": "Prices have been aligned (or lower) to OpenAI models",
|
"tip": "Prices have been aligned (or lower) to OpenAI models",
|
||||||
"learn-more": "Learn more",
|
"learn-more": "Learn more",
|
||||||
|
"dialog-title": "Buy Points",
|
||||||
|
"dialog-desc": "Are you sure you want to buy {{amount}} points?",
|
||||||
|
"dialog-cancel": "Cancel",
|
||||||
|
"dialog-buy": "Buy",
|
||||||
|
"success": "Purchase successful",
|
||||||
|
"success-prompt": "You have successfully purchased {{amount}} points.",
|
||||||
|
"failed": "Purchase failed",
|
||||||
|
"failed-prompt": "Failed to purchase points. Please make sure you have enough balance, you will soon jump to deeptrain wallet to pay balance.",
|
||||||
|
},
|
||||||
|
pkg: {
|
||||||
|
"title": "Packages",
|
||||||
|
"go": "Go to",
|
||||||
|
"verify": "Verify",
|
||||||
|
"cert": "Certification Package",
|
||||||
|
"cert-desc": "After real-name certification, you can get 50 points (worth 5 CNY)",
|
||||||
|
"teen": "Teenager Package",
|
||||||
|
"teen-desc": "After real-name certification, teenagers (18 years old and below) can get an additional 150 points (worth 15 CNY)",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -124,6 +141,23 @@ const resources = {
|
|||||||
"output": "输出",
|
"output": "输出",
|
||||||
"tip": "价格已对齐OpenAI模型或低于官方价格",
|
"tip": "价格已对齐OpenAI模型或低于官方价格",
|
||||||
"learn-more": "了解更多",
|
"learn-more": "了解更多",
|
||||||
|
"dialog-title": "购买点数",
|
||||||
|
"dialog-desc": "您确定要购买 {{amount}} 点数吗?",
|
||||||
|
"dialog-cancel": "取消",
|
||||||
|
"dialog-buy": "购买",
|
||||||
|
"success": "购买成功",
|
||||||
|
"success-prompt": "您已成功购买 {{amount}} 点数。",
|
||||||
|
"failed": "购买失败",
|
||||||
|
"failed-prompt": "购买点数失败。请确保您有足够的余额,您即将跳转到 deeptrain 钱包支付余额。",
|
||||||
|
},
|
||||||
|
pkg: {
|
||||||
|
"title": "礼包",
|
||||||
|
"go": "前往",
|
||||||
|
"verify": "实名认证",
|
||||||
|
"cert": "实名认证礼包",
|
||||||
|
"cert-desc": "实名认证后可获得 50 点数 (价值 5 元)",
|
||||||
|
"teen": "未成年人福利",
|
||||||
|
"teen-desc": "实名认证后未成年人(18 周岁及以下)可额外获得 150 点数 (价值 15 元)",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
41
app/src/routes/Package.tsx
Normal file
41
app/src/routes/Package.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "../components/ui/dialog.tsx";
|
||||||
|
import {Button} from "../components/ui/button.tsx";
|
||||||
|
import "../assets/package.less";
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
|
import {dialogSelector, refreshPackage} from "../store/package.ts";
|
||||||
|
import {useEffect} from "react";
|
||||||
|
|
||||||
|
function Package() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const open = useSelector(dialogSelector);
|
||||||
|
useEffect(() => {
|
||||||
|
refreshPackage(dispatch);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Edit profile</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Make changes to your profile here. Click save when you're done.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button>Save changes</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Package;
|
@ -1,5 +1,5 @@
|
|||||||
import {useDispatch, useSelector} from "react-redux";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
import {dialogSelector, refreshQuota, setDialog} from "../store/quota.ts";
|
import {closeDialog, dialogSelector, refreshQuota, setDialog} from "../store/quota.ts";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {
|
import {
|
||||||
@ -15,6 +15,16 @@ import {Input} from "../components/ui/input.tsx";
|
|||||||
import {testNumberInputEvent} from "../utils.ts";
|
import {testNumberInputEvent} from "../utils.ts";
|
||||||
import {Button} from "../components/ui/button.tsx";
|
import {Button} from "../components/ui/button.tsx";
|
||||||
import {Separator} from "../components/ui/separator.tsx";
|
import {Separator} from "../components/ui/separator.tsx";
|
||||||
|
import {
|
||||||
|
AlertDialog, AlertDialogAction, AlertDialogCancel,
|
||||||
|
AlertDialogContent, AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTrigger
|
||||||
|
} from "../components/ui/alert-dialog.tsx";
|
||||||
|
import {AlertDialogTitle} from "@radix-ui/react-alert-dialog";
|
||||||
|
import {buyQuota} from "../conversation/addition.ts";
|
||||||
|
import {useToast} from "../components/ui/use-toast.ts";
|
||||||
|
|
||||||
type AmountComponentProps = {
|
type AmountComponentProps = {
|
||||||
amount: number;
|
amount: number;
|
||||||
@ -45,8 +55,9 @@ function AmountComponent({ amount, active, other, onClick }: AmountComponentProp
|
|||||||
|
|
||||||
function Quota() {
|
function Quota() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [ current, setCurrent ] = useState(0);
|
const { toast } = useToast();
|
||||||
const [ amount, setAmount ] = useState(0);
|
const [ current, setCurrent ] = useState(1);
|
||||||
|
const [ amount, setAmount ] = useState(10);
|
||||||
const open = useSelector(dialogSelector);
|
const open = useSelector(dialogSelector);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -107,10 +118,43 @@ function Quota() {
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className={`buy-action`}>
|
<div className={`buy-action`}>
|
||||||
<Button variant={`default`} className={`buy-button`}>
|
<AlertDialog>
|
||||||
<Plus className={`h-4 w-4 mr-2`} />
|
<AlertDialogTrigger asChild>
|
||||||
{ t('buy.buy', { amount }) }
|
<Button variant={`default`} className={`buy-button`} disabled={amount === 0}>
|
||||||
</Button>
|
<Plus className={`h-4 w-4 mr-2`} />
|
||||||
|
{ t('buy.buy', { amount }) }
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>{ t('buy.dialog-title') }</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>{ t('buy.dialog-desc', { amount }) }</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>{ t('buy.dialog-cancel') }</AlertDialogCancel>
|
||||||
|
<AlertDialogAction onClick={
|
||||||
|
async () => {
|
||||||
|
const res = await buyQuota(amount);
|
||||||
|
if (res.status) {
|
||||||
|
toast({
|
||||||
|
title: t('buy.success'),
|
||||||
|
description: t('buy.success-prompt', { amount }),
|
||||||
|
})
|
||||||
|
dispatch(closeDialog());
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: t('buy.failed'),
|
||||||
|
description: t('buy.failed-prompt', { amount }),
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
window.open('https://deeptrain.lightxi.com/home/wallet');
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}>{ t('buy.dialog-buy') }</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`line`} />
|
<div className={`line`} />
|
||||||
|
@ -3,6 +3,7 @@ import menuReducer from "./menu";
|
|||||||
import authReducer from "./auth";
|
import authReducer from "./auth";
|
||||||
import chatReducer from "./chat";
|
import chatReducer from "./chat";
|
||||||
import quotaReducer from "./quota";
|
import quotaReducer from "./quota";
|
||||||
|
import packageReducer from "./package";
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
@ -10,6 +11,7 @@ const store = configureStore({
|
|||||||
auth: authReducer,
|
auth: authReducer,
|
||||||
chat: chatReducer,
|
chat: chatReducer,
|
||||||
quota: quotaReducer,
|
quota: quotaReducer,
|
||||||
|
package: packageReducer,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
47
app/src/store/package.ts
Normal file
47
app/src/store/package.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import {createSlice} from "@reduxjs/toolkit";
|
||||||
|
import {getPackage} from "../conversation/addition.ts";
|
||||||
|
|
||||||
|
export const packageSlice = createSlice({
|
||||||
|
name: "package",
|
||||||
|
initialState: {
|
||||||
|
dialog: false,
|
||||||
|
cert: false,
|
||||||
|
teenager: false,
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
toggleDialog: (state) => {
|
||||||
|
state.dialog = !state.dialog;
|
||||||
|
},
|
||||||
|
setDialog: (state, action) => {
|
||||||
|
state.dialog = action.payload as boolean;
|
||||||
|
},
|
||||||
|
openDialog: (state) => {
|
||||||
|
state.dialog = true;
|
||||||
|
},
|
||||||
|
closeDialog: (state) => {
|
||||||
|
state.dialog = false;
|
||||||
|
},
|
||||||
|
refreshState: (state, action) => {
|
||||||
|
state.cert = action.payload.cert;
|
||||||
|
state.teenager = action.payload.teenager;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {toggleDialog, setDialog, openDialog, closeDialog, refreshState} = packageSlice.actions;
|
||||||
|
export default packageSlice.reducer;
|
||||||
|
|
||||||
|
export const dialogSelector = (state: any): boolean => state.package.dialog;
|
||||||
|
export const certSelector = (state: any): boolean => state.package.cert;
|
||||||
|
export const teenagerSelector = (state: any): boolean => state.package.teenager;
|
||||||
|
|
||||||
|
export const refreshPackage = (dispatch: any) => {
|
||||||
|
setInterval(async () => {
|
||||||
|
const current = new Date().getTime(); //@ts-ignore
|
||||||
|
if (window.hasOwnProperty("package") && (current - window.package < 2500)) return; //@ts-ignore
|
||||||
|
window.package = current;
|
||||||
|
|
||||||
|
const response = await getPackage();
|
||||||
|
if (response.status) dispatch(refreshState(response));
|
||||||
|
}, 10000);
|
||||||
|
}
|
@ -75,10 +75,12 @@ func BuyAPI(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
money := float32(form.Quota) * 0.1
|
money := float32(form.Quota) * 0.1
|
||||||
if Pay(user.Username, float32(money)*0.1) {
|
if Pay(user.Username, money) {
|
||||||
|
user.IncreaseQuota(db, float32(form.Quota))
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": true,
|
"status": true,
|
||||||
"data": user.GetQuota(db),
|
"error": "success",
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
|
Loading…
Reference in New Issue
Block a user