mirror of
https://github.com/coaidev/coai.git
synced 2025-05-20 05:20:15 +09:00
fix: random seed seq
This commit is contained in:
parent
6cd5785fe1
commit
e3970c9f6e
@ -6,6 +6,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetInvitationPagination(db *sql.DB, page int64) PaginationForm {
|
func GetInvitationPagination(db *sql.DB, page int64) PaginationForm {
|
||||||
@ -62,12 +63,21 @@ func NewInvitationCode(db *sql.DB, code string, quota float32, t string) error {
|
|||||||
func GenerateInvitations(db *sql.DB, num int, quota float32, t string) InvitationGenerateResponse {
|
func GenerateInvitations(db *sql.DB, num int, quota float32, t string) InvitationGenerateResponse {
|
||||||
arr := make([]string, 0)
|
arr := make([]string, 0)
|
||||||
idx := 0
|
idx := 0
|
||||||
|
retry := 0
|
||||||
for idx < num {
|
for idx < num {
|
||||||
code := fmt.Sprintf("%s-%s", t, utils.GenerateChar(24))
|
code := fmt.Sprintf("%s-%s", t, utils.GenerateChar(24))
|
||||||
if err := NewInvitationCode(db, code, quota, t); err != nil {
|
if err := NewInvitationCode(db, code, quota, t); err != nil {
|
||||||
|
// ignore duplicate code
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if retry < 100 && strings.Contains(err.Error(), "Duplicate entry") {
|
||||||
|
retry++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
retry = 0
|
||||||
return InvitationGenerateResponse{
|
return InvitationGenerateResponse{
|
||||||
Status: false,
|
Status: false,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
|
@ -14,3 +14,29 @@
|
|||||||
min-height: calc(100vh - 56px);
|
min-height: calc(100vh - 56px);
|
||||||
height: max-content;
|
height: max-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.object-id {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
border: 1px solid hsl(var(--border));
|
||||||
|
color: hsl(var(--text-secondary));
|
||||||
|
user-select: none;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
padding: 0.5rem 1.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.25s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: hsl(var(--text));
|
||||||
|
border-color: hsl(var(--border-hover));
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -36,32 +36,6 @@
|
|||||||
color: hsl(var(--text-secondary));
|
color: hsl(var(--text-secondary));
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.target {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-items: center;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
border: 1px solid hsl(var(--border));
|
|
||||||
color: hsl(var(--text-secondary));
|
|
||||||
user-select: none;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
height: 2.5rem;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: 0.25s;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: hsl(var(--text));
|
|
||||||
border-color: hsl(var(--border-hover));
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
transform: translateY(1px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,14 +61,15 @@ function PopupDialog({
|
|||||||
{t("cancel")}
|
{t("cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
loading={true}
|
||||||
onSubmit &&
|
onClick={async () => {
|
||||||
onSubmit(value).then((success) => {
|
if (!onSubmit) return;
|
||||||
if (success) {
|
|
||||||
setOpen(false);
|
const status: boolean = await onSubmit(value);
|
||||||
setValue(defaultValue || "");
|
if (status) {
|
||||||
}
|
setOpen(false);
|
||||||
});
|
setValue(defaultValue || "");
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("confirm")}
|
{t("confirm")}
|
||||||
|
@ -149,6 +149,8 @@ function ChargeEditor({
|
|||||||
return form.models.length === 0;
|
return form.models.length === 0;
|
||||||
}, [form.models]);
|
}, [form.models]);
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
async function post() {
|
async function post() {
|
||||||
const resp = await setCharge(preflight({ ...form }));
|
const resp = await setCharge(preflight({ ...form }));
|
||||||
toastState(toast, t, resp, true);
|
toastState(toast, t, resp, true);
|
||||||
@ -329,7 +331,7 @@ function ChargeEditor({
|
|||||||
<div
|
<div
|
||||||
className={`flex flex-row w-full h-max mt-5 gap-2 items-center flex-wrap`}
|
className={`flex flex-row w-full h-max mt-5 gap-2 items-center flex-wrap`}
|
||||||
>
|
>
|
||||||
<div className={`target`}>
|
<div className={`object-id`}>
|
||||||
<span className={`mr-2`}>ID</span>
|
<span className={`mr-2`}>ID</span>
|
||||||
{form.id === -1 ? (
|
{form.id === -1 ? (
|
||||||
<Plus className={`w-3 h-3`} />
|
<Plus className={`w-3 h-3`} />
|
||||||
@ -349,16 +351,18 @@ function ChargeEditor({
|
|||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={post}
|
onClick={post}
|
||||||
|
loading={true}
|
||||||
|
onLoadingChange={setLoading}
|
||||||
className={`whitespace-nowrap shrink-0`}
|
className={`whitespace-nowrap shrink-0`}
|
||||||
>
|
>
|
||||||
{form.id === -1 ? (
|
{form.id === -1 ? (
|
||||||
<>
|
<>
|
||||||
<Plus className={`w-4 h-4 mr-2`} />
|
{!loading && <Plus className={`w-4 h-4 mr-2`} />}
|
||||||
{t("admin.charge.add-rule")}
|
{t("admin.charge.add-rule")}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<PencilLine className={`w-4 h-4 mr-2`} />
|
{!loading && <PencilLine className={`w-4 h-4 mr-2`} />}
|
||||||
{t("admin.charge.update-rule")}
|
{t("admin.charge.update-rule")}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -19,12 +19,13 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { InvitationForm, InvitationResponse } from "@/admin/types.ts";
|
import { InvitationForm, InvitationResponse } from "@/admin/types.ts";
|
||||||
import { Button } from "@/components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import { ChevronLeft, ChevronRight, RotateCw } from "lucide-react";
|
import { ChevronLeft, ChevronRight, Download, RotateCw } from "lucide-react";
|
||||||
import { useEffectAsync } from "@/utils/hook.ts";
|
import { useEffectAsync } from "@/utils/hook.ts";
|
||||||
import { generateInvitation, getInvitationList } from "@/admin/api/chart.ts";
|
import { generateInvitation, getInvitationList } from "@/admin/api/chart.ts";
|
||||||
import { Input } from "@/components/ui/input.tsx";
|
import { Input } from "@/components/ui/input.tsx";
|
||||||
import { useToast } from "@/components/ui/use-toast.ts";
|
import { useToast } from "@/components/ui/use-toast.ts";
|
||||||
import { Textarea } from "@/components/ui/textarea.tsx";
|
import { Textarea } from "@/components/ui/textarea.tsx";
|
||||||
|
import { saveAsFile } from "@/utils/dom.ts";
|
||||||
|
|
||||||
function GenerateDialog() {
|
function GenerateDialog() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -39,7 +40,7 @@ function GenerateDialog() {
|
|||||||
return value.replace(/[^\d.]/g, "");
|
return value.replace(/[^\d.]/g, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generate() {
|
async function generateCode() {
|
||||||
const data = await generateInvitation(type, Number(quota), Number(number));
|
const data = await generateInvitation(type, Number(quota), Number(number));
|
||||||
if (data.status) setData(data.data.join("\n"));
|
if (data.status) setData(data.data.join("\n"));
|
||||||
else
|
else
|
||||||
@ -58,6 +59,10 @@ function GenerateDialog() {
|
|||||||
setData("");
|
setData("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function downloadCode() {
|
||||||
|
return saveAsFile("invitation.txt", data);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
@ -89,10 +94,10 @@ function GenerateDialog() {
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant={`ghost`} onClick={() => setOpen(false)}>
|
<Button variant={`outline`} onClick={() => setOpen(false)}>
|
||||||
{t("admin.cancel")}
|
{t("admin.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant={`default`} onClick={generate}>
|
<Button variant={`default`} loading={true} onClick={generateCode}>
|
||||||
{t("admin.confirm")}
|
{t("admin.confirm")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
@ -112,7 +117,13 @@ function GenerateDialog() {
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button onClick={close}>{t("close")}</Button>
|
<Button variant={`outline`} onClick={close}>
|
||||||
|
{t("close")}
|
||||||
|
</Button>
|
||||||
|
<Button variant={`default`} onClick={downloadCode}>
|
||||||
|
<Download className={`h-4 w-4 mr-2`} />
|
||||||
|
{t("download")}
|
||||||
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -21,7 +21,7 @@ import { Button } from "@/components/ui/button.tsx";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useMemo, useReducer, useState } from "react";
|
import { useMemo, useReducer, useState } from "react";
|
||||||
import Required from "@/components/Require.tsx";
|
import Required from "@/components/Require.tsx";
|
||||||
import { Search, X } from "lucide-react";
|
import { Plus, Search, X } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@ -410,12 +410,25 @@ function ChannelEditor({ display, id, setEnabled }: ChannelEditorProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`mt-4 flex flex-row w-full h-max pr-2`}>
|
<div className={`mt-4 flex flex-row w-full h-max pr-2 items-center`}>
|
||||||
|
<div className={`object-id`}>
|
||||||
|
<span className={`mr-2`}>ID</span>
|
||||||
|
{edit.id === -1 ? (
|
||||||
|
<Plus className={`w-3 h-3`} />
|
||||||
|
) : (
|
||||||
|
<span className={`id`}>{edit.id}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className={`grow`} />
|
<div className={`grow`} />
|
||||||
<Button variant={`outline`} onClick={() => close()}>
|
<Button variant={`outline`} onClick={() => close()}>
|
||||||
{t("cancel")}
|
{t("cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button className={`ml-2`} onClick={post} disabled={!enabled}>
|
<Button
|
||||||
|
className={`ml-2`}
|
||||||
|
loading={true}
|
||||||
|
onClick={post}
|
||||||
|
disabled={!enabled}
|
||||||
|
>
|
||||||
{t("confirm")}
|
{t("confirm")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,7 +58,12 @@ function ConversationSegment({
|
|||||||
if (state) setOffset(new Date().getTime());
|
if (state) setOffset(new Date().getTime());
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<MoreHorizontal className={`more h-5 w-5 p-0.5`} />
|
<MoreHorizontal className={`more h-5 w-5 p-0.5`} />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
|
@ -2,7 +2,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { selectAuthenticated, selectUsername } from "@/store/auth.ts";
|
import { selectAuthenticated, selectUsername } from "@/store/auth.ts";
|
||||||
import { closeMarket, selectCurrent, selectHistory } from "@/store/chat.ts";
|
import { closeMarket, selectCurrent, selectHistory } from "@/store/chat.ts";
|
||||||
import { useRef, useState } from "react";
|
import React, { useRef, useState } from "react";
|
||||||
import { ConversationInstance } from "@/api/types.ts";
|
import { ConversationInstance } from "@/api/types.ts";
|
||||||
import { useToast } from "@/components/ui/use-toast.ts";
|
import { useToast } from "@/components/ui/use-toast.ts";
|
||||||
import { extractMessage, filterMessage } from "@/utils/processor.ts";
|
import { extractMessage, filterMessage } from "@/utils/processor.ts";
|
||||||
@ -65,6 +65,26 @@ function SidebarAction({ setOperateConversation }: SidebarActionProps) {
|
|||||||
const refresh = useRef(null);
|
const refresh = useRef(null);
|
||||||
const [removeAll, setRemoveAll] = useState<boolean>(false);
|
const [removeAll, setRemoveAll] = useState<boolean>(false);
|
||||||
|
|
||||||
|
async function handleDeleteAll(e: React.MouseEvent<HTMLButtonElement>) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (await deleteAllConversations(dispatch))
|
||||||
|
toast({
|
||||||
|
title: t("conversation.delete-success"),
|
||||||
|
description: t("conversation.delete-success-prompt"),
|
||||||
|
});
|
||||||
|
else
|
||||||
|
toast({
|
||||||
|
title: t("conversation.delete-failed"),
|
||||||
|
description: t("conversation.delete-failed-prompt"),
|
||||||
|
});
|
||||||
|
|
||||||
|
await updateConversationList(dispatch);
|
||||||
|
setOperateConversation({ target: null, type: "" });
|
||||||
|
setRemoveAll(false);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`sidebar-action`}>
|
<div className={`sidebar-action`}>
|
||||||
<Button
|
<Button
|
||||||
@ -96,27 +116,7 @@ function SidebarAction({ setOperateConversation }: SidebarActionProps) {
|
|||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>{t("conversation.cancel")}</AlertDialogCancel>
|
<AlertDialogCancel>{t("conversation.cancel")}</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction onClick={handleDeleteAll}>
|
||||||
onClick={async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (await deleteAllConversations(dispatch))
|
|
||||||
toast({
|
|
||||||
title: t("conversation.delete-success"),
|
|
||||||
description: t("conversation.delete-success-prompt"),
|
|
||||||
});
|
|
||||||
else
|
|
||||||
toast({
|
|
||||||
title: t("conversation.delete-failed"),
|
|
||||||
description: t("conversation.delete-failed-prompt"),
|
|
||||||
});
|
|
||||||
|
|
||||||
await updateConversationList(dispatch);
|
|
||||||
setOperateConversation({ target: null, type: "" });
|
|
||||||
setRemoveAll(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("conversation.delete")}
|
{t("conversation.delete")}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
@ -157,6 +157,40 @@ function SidebarConversationList({
|
|||||||
const [shared, setShared] = useState<string>("");
|
const [shared, setShared] = useState<string>("");
|
||||||
const current = useSelector(selectCurrent);
|
const current = useSelector(selectCurrent);
|
||||||
|
|
||||||
|
async function handleDelete(e: React.MouseEvent<HTMLButtonElement>) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (
|
||||||
|
await deleteConversation(dispatch, operateConversation?.target?.id || -1)
|
||||||
|
)
|
||||||
|
toast({
|
||||||
|
title: t("conversation.delete-success"),
|
||||||
|
description: t("conversation.delete-success-prompt"),
|
||||||
|
});
|
||||||
|
else
|
||||||
|
toast({
|
||||||
|
title: t("conversation.delete-failed"),
|
||||||
|
description: t("conversation.delete-failed-prompt"),
|
||||||
|
});
|
||||||
|
setOperateConversation({ target: null, type: "" });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleShare(e: React.MouseEvent<HTMLButtonElement>) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const resp = await shareConversation(operateConversation?.target?.id || -1);
|
||||||
|
if (resp.status) setShared(getSharedLink(resp.data));
|
||||||
|
else
|
||||||
|
toast({
|
||||||
|
title: t("share.failed"),
|
||||||
|
description: resp.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
setOperateConversation({ target: null, type: "" });
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`conversation-list`}>
|
<div className={`conversation-list`}>
|
||||||
@ -198,29 +232,7 @@ function SidebarConversationList({
|
|||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>{t("conversation.cancel")}</AlertDialogCancel>
|
<AlertDialogCancel>{t("conversation.cancel")}</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction onClick={handleDelete}>
|
||||||
onClick={async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (
|
|
||||||
await deleteConversation(
|
|
||||||
dispatch,
|
|
||||||
operateConversation?.target?.id || -1,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
toast({
|
|
||||||
title: t("conversation.delete-success"),
|
|
||||||
description: t("conversation.delete-success-prompt"),
|
|
||||||
});
|
|
||||||
else
|
|
||||||
toast({
|
|
||||||
title: t("conversation.delete-failed"),
|
|
||||||
description: t("conversation.delete-failed-prompt"),
|
|
||||||
});
|
|
||||||
setOperateConversation({ target: null, type: "" });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("conversation.delete")}
|
{t("conversation.delete")}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
@ -250,24 +262,7 @@ function SidebarConversationList({
|
|||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>{t("conversation.cancel")}</AlertDialogCancel>
|
<AlertDialogCancel>{t("conversation.cancel")}</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction onClick={handleShare}>
|
||||||
onClick={async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
const resp = await shareConversation(
|
|
||||||
operateConversation?.target?.id || -1,
|
|
||||||
);
|
|
||||||
if (resp.status) setShared(getSharedLink(resp.data));
|
|
||||||
else
|
|
||||||
toast({
|
|
||||||
title: t("share.failed"),
|
|
||||||
description: resp.message,
|
|
||||||
});
|
|
||||||
|
|
||||||
setOperateConversation({ target: null, type: "" });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("share.title")}
|
{t("share.title")}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
|
@ -3,7 +3,7 @@ import { Slot } from "@radix-ui/react-slot";
|
|||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "./lib/utils";
|
import { cn } from "./lib/utils";
|
||||||
import { useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
@ -40,6 +40,7 @@ export interface ButtonProps
|
|||||||
VariantProps<typeof buttonVariants> {
|
VariantProps<typeof buttonVariants> {
|
||||||
asChild?: boolean;
|
asChild?: boolean;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
onLoadingChange?: (loading: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
@ -53,6 +54,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
children,
|
children,
|
||||||
asChild = false,
|
asChild = false,
|
||||||
loading = false,
|
loading = false,
|
||||||
|
onLoadingChange,
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
@ -77,6 +79,24 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
}
|
}
|
||||||
: onClick;
|
: onClick;
|
||||||
|
|
||||||
|
loading &&
|
||||||
|
onLoadingChange &&
|
||||||
|
useEffect(() => {
|
||||||
|
onLoadingChange(working);
|
||||||
|
}, [working]);
|
||||||
|
|
||||||
|
const child = useMemo(() => {
|
||||||
|
if (asChild) return children;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{loading && working && (
|
||||||
|
<Loader2 className={`animate-spin w-4 h-4 mr-2`} />
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}, [asChild, children, loading, working]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
@ -85,11 +105,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
disabled={disabled || working}
|
disabled={disabled || working}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{loading && working && (
|
{child}
|
||||||
<Loader2 className={`animate-spin w-4 h-4 mr-2`} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{children}
|
|
||||||
</Comp>
|
</Comp>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -22,6 +22,7 @@ const resources = {
|
|||||||
"login-require": "您需要登录才能使用此功能",
|
"login-require": "您需要登录才能使用此功能",
|
||||||
logout: "登出",
|
logout: "登出",
|
||||||
quota: "点数",
|
quota: "点数",
|
||||||
|
download: "下载",
|
||||||
"try-again": "重试",
|
"try-again": "重试",
|
||||||
"invalid-token": "无效的令牌",
|
"invalid-token": "无效的令牌",
|
||||||
"invalid-token-prompt": "请重试。",
|
"invalid-token-prompt": "请重试。",
|
||||||
@ -460,6 +461,7 @@ const resources = {
|
|||||||
"login-require": "You need to login to use this feature",
|
"login-require": "You need to login to use this feature",
|
||||||
logout: "Logout",
|
logout: "Logout",
|
||||||
quota: "Quota",
|
quota: "Quota",
|
||||||
|
download: "Download",
|
||||||
"try-again": "Try again",
|
"try-again": "Try again",
|
||||||
"invalid-token": "Invalid token",
|
"invalid-token": "Invalid token",
|
||||||
"invalid-token-prompt": "Please try again.",
|
"invalid-token-prompt": "Please try again.",
|
||||||
@ -921,6 +923,7 @@ const resources = {
|
|||||||
"login-require": "Вам нужно войти, чтобы использовать эту функцию",
|
"login-require": "Вам нужно войти, чтобы использовать эту функцию",
|
||||||
logout: "Выйти",
|
logout: "Выйти",
|
||||||
quota: "Квота",
|
quota: "Квота",
|
||||||
|
download: "Скачать",
|
||||||
"try-again": "Попробуйте еще раз",
|
"try-again": "Попробуйте еще раз",
|
||||||
"invalid-token": "Неверный токен",
|
"invalid-token": "Неверный токен",
|
||||||
"invalid-token-prompt": "Пожалуйста, попробуйте еще раз.",
|
"invalid-token-prompt": "Пожалуйста, попробуйте еще раз.",
|
||||||
|
1
channel/system.go
Normal file
1
channel/system.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package channel
|
@ -13,6 +13,21 @@ func Intn(n int) int {
|
|||||||
return r.Intn(n)
|
return r.Intn(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IntnSeed(n int, seed int) int {
|
||||||
|
// unix nano is the same if called in the same nanosecond, so we need to add another random seed
|
||||||
|
source := rand.NewSource(time.Now().UnixNano() + int64(seed))
|
||||||
|
r := rand.New(source)
|
||||||
|
return r.Intn(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IntnSeq(n int, len int) (res []int) {
|
||||||
|
for i := 0; i < len; i++ {
|
||||||
|
res = append(res, IntnSeed(n, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func Sum[T int | int64 | float32 | float64](arr []T) T {
|
func Sum[T int | int64 | float32 | float64](arr []T) T {
|
||||||
var res T
|
var res T
|
||||||
for _, v := range arr {
|
for _, v := range arr {
|
||||||
|
@ -14,18 +14,22 @@ func GetRandomInt(min int, max int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GenerateCode(length int) string {
|
func GenerateCode(length int) string {
|
||||||
|
seq := IntnSeq(10, length)
|
||||||
|
|
||||||
var code string
|
var code string
|
||||||
for i := 0; i < length; i++ {
|
for i := 0; i < length; i++ {
|
||||||
code += strconv.Itoa(Intn(10))
|
code += strconv.Itoa(seq[i])
|
||||||
}
|
}
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateChar(length int) string {
|
func GenerateChar(length int) string {
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
seq := IntnSeq(len(charset), length)
|
||||||
|
|
||||||
result := make([]byte, length)
|
result := make([]byte, length)
|
||||||
for i := 0; i < length; i++ {
|
for i := 0; i < length; i++ {
|
||||||
result[i] = charset[Intn(len(charset))]
|
result[i] = charset[seq[i]]
|
||||||
}
|
}
|
||||||
return string(result)
|
return string(result)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user