diff --git a/api/chat.go b/api/chat.go
index bf32e12..8b91056 100644
--- a/api/chat.go
+++ b/api/chat.go
@@ -60,8 +60,9 @@ func TextChat(db *sql.DB, user *auth.User, conn *websocket.Conn, instance *conve
SendSegmentMessage(conn, types.ChatGPTSegmentResponse{
Message: defaultErrorMessage,
Quota: buffer.GetQuota(),
- End: false,
+ End: true,
})
+ return defaultErrorMessage
}
// collect quota
diff --git a/app/src/App.tsx b/app/src/App.tsx
index cc327ed..c69bb6a 100644
--- a/app/src/App.tsx
+++ b/app/src/App.tsx
@@ -29,6 +29,7 @@ import {login, tokenField} from "./conf.ts";
import { useTranslation } from "react-i18next";
import Quota from "./routes/Quota.tsx";
import {openDialog} from "./store/quota.ts";
+import Package from "./routes/Package.tsx";
function Settings() {
const { t } = useTranslation();
@@ -113,6 +114,7 @@ function App() {
+
);
}
diff --git a/app/src/assets/package.less b/app/src/assets/package.less
new file mode 100644
index 0000000..e69de29
diff --git a/app/src/assets/quota.less b/app/src/assets/quota.less
index 59314ab..2714245 100644
--- a/app/src/assets/quota.less
+++ b/app/src/assets/quota.less
@@ -30,6 +30,7 @@
.buy-button {
width: 100%;
+ transition: .25s;
}
}
diff --git a/app/src/conversation/addition.ts b/app/src/conversation/addition.ts
new file mode 100644
index 0000000..fda333a
--- /dev/null
+++ b/app/src/conversation/addition.ts
@@ -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 {
+ 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 {
+ try {
+ const resp = await axios.get(`/package`);
+ return resp.data as PackageResponse;
+ } catch (e) {
+ console.debug(e);
+ return { status: false, cert: false, teenager: false };
+ }
+}
diff --git a/app/src/i18n.ts b/app/src/i18n.ts
index e4457cc..c0875a8 100644
--- a/app/src/i18n.ts
+++ b/app/src/i18n.ts
@@ -66,6 +66,23 @@ const resources = {
"output": "Output",
"tip": "Prices have been aligned (or lower) to OpenAI models",
"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": "输出",
"tip": "价格已对齐OpenAI模型或低于官方价格",
"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 元)",
}
},
},
diff --git a/app/src/routes/Package.tsx b/app/src/routes/Package.tsx
new file mode 100644
index 0000000..36de917
--- /dev/null
+++ b/app/src/routes/Package.tsx
@@ -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 (
+
+ )
+}
+
+export default Package;
diff --git a/app/src/routes/Quota.tsx b/app/src/routes/Quota.tsx
index cb3176e..335f8ab 100644
--- a/app/src/routes/Quota.tsx
+++ b/app/src/routes/Quota.tsx
@@ -1,5 +1,5 @@
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 {useEffect, useState} from "react";
import {
@@ -15,6 +15,16 @@ import {Input} from "../components/ui/input.tsx";
import {testNumberInputEvent} from "../utils.ts";
import {Button} from "../components/ui/button.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 = {
amount: number;
@@ -45,8 +55,9 @@ function AmountComponent({ amount, active, other, onClick }: AmountComponentProp
function Quota() {
const { t } = useTranslation();
- const [ current, setCurrent ] = useState(0);
- const [ amount, setAmount ] = useState(0);
+ const { toast } = useToast();
+ const [ current, setCurrent ] = useState(1);
+ const [ amount, setAmount ] = useState(10);
const open = useSelector(dialogSelector);
const dispatch = useDispatch();
useEffect(() => {
@@ -107,10 +118,43 @@ function Quota() {
}
-
+
+
+
+
+
+
+ { t('buy.dialog-title') }
+ { t('buy.dialog-desc', { amount }) }
+
+
+ { t('buy.dialog-cancel') }
+ {
+ 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') }
+
+
+
diff --git a/app/src/store/index.ts b/app/src/store/index.ts
index 786d26f..dc0d64c 100644
--- a/app/src/store/index.ts
+++ b/app/src/store/index.ts
@@ -3,6 +3,7 @@ import menuReducer from "./menu";
import authReducer from "./auth";
import chatReducer from "./chat";
import quotaReducer from "./quota";
+import packageReducer from "./package";
const store = configureStore({
reducer: {
@@ -10,6 +11,7 @@ const store = configureStore({
auth: authReducer,
chat: chatReducer,
quota: quotaReducer,
+ package: packageReducer,
},
});
diff --git a/app/src/store/package.ts b/app/src/store/package.ts
new file mode 100644
index 0000000..c3c6c0a
--- /dev/null
+++ b/app/src/store/package.ts
@@ -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);
+}
diff --git a/auth/controller.go b/auth/controller.go
index 5f5b9b0..44d9db7 100644
--- a/auth/controller.go
+++ b/auth/controller.go
@@ -75,10 +75,12 @@ func BuyAPI(c *gin.Context) {
}
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{
"status": true,
- "data": user.GetQuota(db),
+ "error": "success",
})
} else {
c.JSON(200, gin.H{