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"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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 {
|
||||
arr := make([]string, 0)
|
||||
idx := 0
|
||||
retry := 0
|
||||
for idx < num {
|
||||
code := fmt.Sprintf("%s-%s", t, utils.GenerateChar(24))
|
||||
if err := NewInvitationCode(db, code, quota, t); err != nil {
|
||||
// ignore duplicate code
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
continue
|
||||
}
|
||||
|
||||
if retry < 100 && strings.Contains(err.Error(), "Duplicate entry") {
|
||||
retry++
|
||||
continue
|
||||
}
|
||||
|
||||
retry = 0
|
||||
return InvitationGenerateResponse{
|
||||
Status: false,
|
||||
Message: err.Error(),
|
||||
|
@ -14,3 +14,29 @@
|
||||
min-height: calc(100vh - 56px);
|
||||
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));
|
||||
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")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onSubmit &&
|
||||
onSubmit(value).then((success) => {
|
||||
if (success) {
|
||||
loading={true}
|
||||
onClick={async () => {
|
||||
if (!onSubmit) return;
|
||||
|
||||
const status: boolean = await onSubmit(value);
|
||||
if (status) {
|
||||
setOpen(false);
|
||||
setValue(defaultValue || "");
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("confirm")}
|
||||
|
@ -149,6 +149,8 @@ function ChargeEditor({
|
||||
return form.models.length === 0;
|
||||
}, [form.models]);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
async function post() {
|
||||
const resp = await setCharge(preflight({ ...form }));
|
||||
toastState(toast, t, resp, true);
|
||||
@ -329,7 +331,7 @@ function ChargeEditor({
|
||||
<div
|
||||
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>
|
||||
{form.id === -1 ? (
|
||||
<Plus className={`w-3 h-3`} />
|
||||
@ -349,16 +351,18 @@ function ChargeEditor({
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={post}
|
||||
loading={true}
|
||||
onLoadingChange={setLoading}
|
||||
className={`whitespace-nowrap shrink-0`}
|
||||
>
|
||||
{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")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PencilLine className={`w-4 h-4 mr-2`} />
|
||||
{!loading && <PencilLine className={`w-4 h-4 mr-2`} />}
|
||||
{t("admin.charge.update-rule")}
|
||||
</>
|
||||
)}
|
||||
|
@ -19,12 +19,13 @@ import { useTranslation } from "react-i18next";
|
||||
import { useState } from "react";
|
||||
import { InvitationForm, InvitationResponse } from "@/admin/types.ts";
|
||||
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 { generateInvitation, getInvitationList } from "@/admin/api/chart.ts";
|
||||
import { Input } from "@/components/ui/input.tsx";
|
||||
import { useToast } from "@/components/ui/use-toast.ts";
|
||||
import { Textarea } from "@/components/ui/textarea.tsx";
|
||||
import { saveAsFile } from "@/utils/dom.ts";
|
||||
|
||||
function GenerateDialog() {
|
||||
const { t } = useTranslation();
|
||||
@ -39,7 +40,7 @@ function GenerateDialog() {
|
||||
return value.replace(/[^\d.]/g, "");
|
||||
}
|
||||
|
||||
async function generate() {
|
||||
async function generateCode() {
|
||||
const data = await generateInvitation(type, Number(quota), Number(number));
|
||||
if (data.status) setData(data.data.join("\n"));
|
||||
else
|
||||
@ -58,6 +59,10 @@ function GenerateDialog() {
|
||||
setData("");
|
||||
}
|
||||
|
||||
function downloadCode() {
|
||||
return saveAsFile("invitation.txt", data);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
@ -89,10 +94,10 @@ function GenerateDialog() {
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant={`ghost`} onClick={() => setOpen(false)}>
|
||||
<Button variant={`outline`} onClick={() => setOpen(false)}>
|
||||
{t("admin.cancel")}
|
||||
</Button>
|
||||
<Button variant={`default`} onClick={generate}>
|
||||
<Button variant={`default`} loading={true} onClick={generateCode}>
|
||||
{t("admin.confirm")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
@ -112,7 +117,13 @@ function GenerateDialog() {
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<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>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
@ -21,7 +21,7 @@ import { Button } from "@/components/ui/button.tsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMemo, useReducer, useState } from "react";
|
||||
import Required from "@/components/Require.tsx";
|
||||
import { Search, X } from "lucide-react";
|
||||
import { Plus, Search, X } from "lucide-react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@ -410,12 +410,25 @@ function ChannelEditor({ display, id, setEnabled }: ChannelEditorProps) {
|
||||
/>
|
||||
</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`} />
|
||||
<Button variant={`outline`} onClick={() => close()}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<Button className={`ml-2`} onClick={post} disabled={!enabled}>
|
||||
<Button
|
||||
className={`ml-2`}
|
||||
loading={true}
|
||||
onClick={post}
|
||||
disabled={!enabled}
|
||||
>
|
||||
{t("confirm")}
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -58,7 +58,12 @@ function ConversationSegment({
|
||||
if (state) setOffset(new Date().getTime());
|
||||
}}
|
||||
>
|
||||
<DropdownMenuTrigger>
|
||||
<DropdownMenuTrigger
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<MoreHorizontal className={`more h-5 w-5 p-0.5`} />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
|
@ -2,7 +2,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { selectAuthenticated, selectUsername } from "@/store/auth.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 { useToast } from "@/components/ui/use-toast.ts";
|
||||
import { extractMessage, filterMessage } from "@/utils/processor.ts";
|
||||
@ -65,6 +65,26 @@ function SidebarAction({ setOperateConversation }: SidebarActionProps) {
|
||||
const refresh = useRef(null);
|
||||
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 (
|
||||
<div className={`sidebar-action`}>
|
||||
<Button
|
||||
@ -96,27 +116,7 @@ function SidebarAction({ setOperateConversation }: SidebarActionProps) {
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("conversation.cancel")}</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
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);
|
||||
}}
|
||||
>
|
||||
<AlertDialogAction onClick={handleDeleteAll}>
|
||||
{t("conversation.delete")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
@ -157,6 +157,40 @@ function SidebarConversationList({
|
||||
const [shared, setShared] = useState<string>("");
|
||||
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 (
|
||||
<>
|
||||
<div className={`conversation-list`}>
|
||||
@ -198,29 +232,7 @@ function SidebarConversationList({
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("conversation.cancel")}</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
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: "" });
|
||||
}}
|
||||
>
|
||||
<AlertDialogAction onClick={handleDelete}>
|
||||
{t("conversation.delete")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
@ -250,24 +262,7 @@ function SidebarConversationList({
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("conversation.cancel")}</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
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: "" });
|
||||
}}
|
||||
>
|
||||
<AlertDialogAction onClick={handleShare}>
|
||||
{t("share.title")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
|
@ -3,7 +3,7 @@ import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "./lib/utils";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
const buttonVariants = cva(
|
||||
@ -40,6 +40,7 @@ export interface ButtonProps
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
loading?: boolean;
|
||||
onLoadingChange?: (loading: boolean) => void;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
@ -53,6 +54,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
children,
|
||||
asChild = false,
|
||||
loading = false,
|
||||
onLoadingChange,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
@ -77,6 +79,24 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
}
|
||||
: 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 (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
@ -85,11 +105,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
disabled={disabled || working}
|
||||
{...props}
|
||||
>
|
||||
{loading && working && (
|
||||
<Loader2 className={`animate-spin w-4 h-4 mr-2`} />
|
||||
)}
|
||||
|
||||
{children}
|
||||
{child}
|
||||
</Comp>
|
||||
);
|
||||
},
|
||||
|
@ -22,6 +22,7 @@ const resources = {
|
||||
"login-require": "您需要登录才能使用此功能",
|
||||
logout: "登出",
|
||||
quota: "点数",
|
||||
download: "下载",
|
||||
"try-again": "重试",
|
||||
"invalid-token": "无效的令牌",
|
||||
"invalid-token-prompt": "请重试。",
|
||||
@ -460,6 +461,7 @@ const resources = {
|
||||
"login-require": "You need to login to use this feature",
|
||||
logout: "Logout",
|
||||
quota: "Quota",
|
||||
download: "Download",
|
||||
"try-again": "Try again",
|
||||
"invalid-token": "Invalid token",
|
||||
"invalid-token-prompt": "Please try again.",
|
||||
@ -921,6 +923,7 @@ const resources = {
|
||||
"login-require": "Вам нужно войти, чтобы использовать эту функцию",
|
||||
logout: "Выйти",
|
||||
quota: "Квота",
|
||||
download: "Скачать",
|
||||
"try-again": "Попробуйте еще раз",
|
||||
"invalid-token": "Неверный токен",
|
||||
"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)
|
||||
}
|
||||
|
||||
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 {
|
||||
var res T
|
||||
for _, v := range arr {
|
||||
|
@ -14,18 +14,22 @@ func GetRandomInt(min int, max int) int {
|
||||
}
|
||||
|
||||
func GenerateCode(length int) string {
|
||||
seq := IntnSeq(10, length)
|
||||
|
||||
var code string
|
||||
for i := 0; i < length; i++ {
|
||||
code += strconv.Itoa(Intn(10))
|
||||
code += strconv.Itoa(seq[i])
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
func GenerateChar(length int) string {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
seq := IntnSeq(len(charset), length)
|
||||
|
||||
result := make([]byte, length)
|
||||
for i := 0; i < length; i++ {
|
||||
result[i] = charset[Intn(len(charset))]
|
||||
result[i] = charset[seq[i]]
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user