diff --git a/addition/article/api.go b/addition/article/api.go index 7bf6fec..bccac1e 100644 --- a/addition/article/api.go +++ b/addition/article/api.go @@ -2,6 +2,7 @@ package article import ( "chat/auth" + "chat/globals" "chat/utils" "fmt" "github.com/gin-gonic/gin" @@ -48,7 +49,7 @@ func GenerateAPI(c *gin.Context) { user := auth.ParseToken(c, form.Token) db := utils.GetDBFromContext(c) - if !(user != nil && user.IsSubscribe(db)) { + if !auth.HitGroups(db, user, globals.ArticlePermissionGroup) { return } diff --git a/addition/generation/api.go b/addition/generation/api.go index b56b92c..35f666b 100644 --- a/addition/generation/api.go +++ b/addition/generation/api.go @@ -6,7 +6,6 @@ import ( "chat/utils" "fmt" "github.com/gin-gonic/gin" - "strconv" "strings" ) @@ -41,22 +40,15 @@ func GenerateAPI(c *gin.Context) { } user := auth.ParseToken(c, form.Token) - authenticated := user != nil db := utils.GetDBFromContext(c) cache := utils.GetCacheFromContext(c) - id := auth.GetId(db, user) - - if !utils.IncrWithLimit(cache, - fmt.Sprintf(":generation:%s", utils.Multi[string](authenticated, strconv.FormatInt(id, 10), c.ClientIP())), - 1, - 30, - 3600, - ) { + if !auth.HitGroups(db, user, globals.GenerationPermissionGroup) { conn.Send(globals.GenerationSegmentResponse{ - End: true, - Error: "generation rate limit exceeded, the max generation rate is 30 per hour.", + Message: "permission denied", + Quota: 0, + End: true, }) return } @@ -76,7 +68,6 @@ func GenerateAPI(c *gin.Context) { auth.GetGroup(db, user), form.Model, form.Prompt, - plan, func(buffer *utils.Buffer, data string) { instance = buffer conn.Send(globals.GenerationSegmentResponse{ diff --git a/addition/generation/generate.go b/addition/generation/generate.go index 3e931a3..e8ba285 100644 --- a/addition/generation/generate.go +++ b/addition/generation/generate.go @@ -6,10 +6,10 @@ import ( "fmt" ) -func CreateGenerationWithCache(group, model, prompt string, enableReverse bool, hook func(buffer *utils.Buffer, data string)) (string, error) { +func CreateGenerationWithCache(group, model, prompt string, hook func(buffer *utils.Buffer, data string)) (string, error) { hash, path := GetFolderByHash(model, prompt) if !utils.Exists(path) { - if err := CreateGeneration(group, model, prompt, path, enableReverse, hook); err != nil { + if err := CreateGeneration(group, model, prompt, path, hook); err != nil { globals.Info(fmt.Sprintf("[project] error during generation %s (model %s): %s", prompt, model, err.Error())) return "", fmt.Errorf("error during generate project: %s", err.Error()) } diff --git a/addition/generation/prompt.go b/addition/generation/prompt.go index e8aaba3..e3decdc 100644 --- a/addition/generation/prompt.go +++ b/addition/generation/prompt.go @@ -13,7 +13,7 @@ type ProjectResult struct { Result map[string]interface{} `json:"result"` } -func CreateGeneration(group, model, prompt, path string, plan bool, hook func(buffer *utils.Buffer, data string)) error { +func CreateGeneration(group, model, prompt, path string, hook func(buffer *utils.Buffer, data string)) error { message := GenerateMessage(prompt) buffer := utils.NewBuffer(model, message, channel.ChargeInstance.GetCharge(model)) diff --git a/app/src/admin/api/info.ts b/app/src/admin/api/info.ts index dda6f1a..8b4b204 100644 --- a/app/src/admin/api/info.ts +++ b/app/src/admin/api/info.ts @@ -18,6 +18,8 @@ export type SiteInfo = { buy_link: string; mail: boolean; contact: string; + article: string[]; + generation: string[]; }; export async function getSiteInfo(): Promise { @@ -35,6 +37,8 @@ export async function getSiteInfo(): Promise { buy_link: "", contact: "", mail: false, + article: [], + generation: [], }; } } @@ -50,10 +54,6 @@ export function syncSiteInfo() { setAnnouncement(info.announcement); setBuyLink(info.buy_link); - infoEvent.emit({ - mail: info.mail, - contact: info.contact, - } as InfoForm); - console.log(info); + infoEvent.emit(info as InfoForm); }, 25); } diff --git a/app/src/admin/api/system.ts b/app/src/admin/api/system.ts index f765f7e..79876d6 100644 --- a/app/src/admin/api/system.ts +++ b/app/src/admin/api/system.ts @@ -37,11 +37,17 @@ export type SiteState = { contact: string; }; +export type CommonState = { + article: string[]; + generation: string[]; +}; + export type SystemProps = { general: GeneralState; site: SiteState; mail: MailState; search: SearchState; + common: CommonState; }; export type SystemResponse = CommonResponse & { @@ -126,4 +132,8 @@ export const initialSystemState: SystemProps = { endpoint: "https://duckduckgo-api.vercel.app", query: 5, }, + common: { + article: [], + generation: [], + }, }; diff --git a/app/src/admin/channel.ts b/app/src/admin/channel.ts index 9eacf75..acb9f0b 100644 --- a/app/src/admin/channel.ts +++ b/app/src/admin/channel.ts @@ -1,4 +1,11 @@ import { getUniqueList } from "@/utils/base.ts"; +import { + AnonymousType, + BasicType, + NormalType, + ProType, + StandardType, +} from "@/utils/groups.ts"; export type Channel = { id: number; @@ -213,11 +220,11 @@ export const channelModels: string[] = getUniqueList( ); export const channelGroups: string[] = [ - "anonymous", - "normal", - "basic", - "standard", - "pro", + AnonymousType, + NormalType, + BasicType, + StandardType, + ProType, ]; export function getChannelInfo(type?: string): ChannelInfo { diff --git a/app/src/components/FileViewer.tsx b/app/src/components/FileViewer.tsx index c82e606..4d9c797 100644 --- a/app/src/components/FileViewer.tsx +++ b/app/src/components/FileViewer.tsx @@ -40,12 +40,7 @@ function FileViewer({ filename, content, children, asChild }: FileViewerProps) {
- + setRenderedType(viewerType.Text)} diff --git a/app/src/components/home/ChatSpace.tsx b/app/src/components/home/ChatSpace.tsx index 121fcd9..e0cb076 100644 --- a/app/src/components/home/ChatSpace.tsx +++ b/app/src/components/home/ChatSpace.tsx @@ -15,8 +15,13 @@ import { import { getLanguage } from "@/i18n.ts"; import { selectAuthenticated } from "@/store/auth.ts"; import { appLogo } from "@/conf/env.ts"; -import { infoContactSelector } from "@/store/info.ts"; +import { + infoArticleSelector, + infoContactSelector, + infoGenerationSelector, +} from "@/store/info.ts"; import Markdown from "@/components/Markdown.tsx"; +import { hitGroup } from "@/utils/groups.ts"; function ChatSpace() { const [open, setOpen] = useState(false); @@ -27,6 +32,12 @@ function ChatSpace() { const cn = getLanguage() === "cn"; const auth = useSelector(selectAuthenticated); + const generationGroup = useSelector(infoGenerationSelector); + const generation = hitGroup(generationGroup); + + const articleGroup = useSelector(infoArticleSelector); + const article = hitGroup(articleGroup); + return (
)} - {subscription && ( + + {article && ( )} - + + {generation && ( + + )} diff --git a/app/src/components/ui/multi-combobox.tsx b/app/src/components/ui/multi-combobox.tsx index 2980735..cca6211 100644 --- a/app/src/components/ui/multi-combobox.tsx +++ b/app/src/components/ui/multi-combobox.tsx @@ -50,6 +50,8 @@ export function MultiCombobox({ return [...set]; }, [list]); + const v = value ?? []; + return ( @@ -61,7 +63,7 @@ export function MultiCombobox({ disabled={disabled} > - {placeholder ?? `${value.length} Items Selected`} + {placeholder ?? `${v.length} Items Selected`} @@ -75,17 +77,17 @@ export function MultiCombobox({ key={key} value={key} onSelect={(current) => { - if (value.includes(current)) { - onChange(value.filter((item) => item !== current)); + if (v.includes(current)) { + onChange(v.filter((item) => item !== current)); } else { - onChange([...value, current]); + onChange([...v, current]); } }} > {listTranslate ? t(`${listTranslate}.${key}`) : key} diff --git a/app/src/events/info.ts b/app/src/events/info.ts index 46d08bc..731b951 100644 --- a/app/src/events/info.ts +++ b/app/src/events/info.ts @@ -3,6 +3,8 @@ import { EventCommitter } from "@/events/struct.ts"; export type InfoForm = { mail: boolean; contact: string; + article: string[]; + generation: string[]; }; export const infoEvent = new EventCommitter({ diff --git a/app/src/resources/i18n/cn.json b/app/src/resources/i18n/cn.json index 81bfc5e..b40d3d3 100644 --- a/app/src/resources/i18n/cn.json +++ b/app/src/resources/i18n/cn.json @@ -583,7 +583,8 @@ "normal": "普通用户", "basic": "基础版订阅用户", "standard": "标准版订阅用户", - "pro": "专业版订阅用户" + "pro": "专业版订阅用户", + "admin": "管理员用户" } }, "charge": { @@ -622,6 +623,7 @@ "search": "联网搜索", "site": "站点设置", "mail": "SMTP 发件设置", + "common": "通用设置", "save": "保存", "updateRoot": "修改 Root 密码", "updateRootTip": "请谨慎操作,修改 Root 密码后,您需要重新登录。", @@ -661,7 +663,12 @@ "announcement": "站点公告", "announcementPlaceholder": "请输入站点公告 (支持 Markdown / HTML 格式)", "contact": "联系信息", - "contactPlaceholder": "请输入联系信息 (支持 Markdown / HTML 格式)" + "contactPlaceholder": "请输入联系信息 (支持 Markdown / HTML 格式)", + "article": "批量文章生成功能分组", + "articleTip": "批量文章生成功能分组,勾选后当前用户组可使用批量文章生成功能", + "generate": "AI 项目生成器分组", + "generateTip": "AI 项目生成器分组,勾选后当前用户组可使用 AI 项目生成器", + "groupPlaceholder": "已选 {{length}} 个分组" }, "logger": { "title": "服务日志", diff --git a/app/src/resources/i18n/en.json b/app/src/resources/i18n/en.json index e1bd321..00ecb13 100644 --- a/app/src/resources/i18n/en.json +++ b/app/src/resources/i18n/en.json @@ -422,7 +422,8 @@ "normal": "Normal", "basic": "Basic Subscribers", "standard": "Standard Subscribers", - "pro": "Pro Subscribers" + "pro": "Pro Subscribers", + "admin": "Admin user" }, "joint": "Dock upstream", "joint-endpoint": "Upstream address", @@ -509,7 +510,13 @@ "buyLinkPlaceholder": "Please enter the card secret purchase link, leave blank to not show the purchase button", "mailConfNotValid": "SMTP send parameters are not configured correctly, mailbox verification is disabled", "contact": "Contact Information", - "contactPlaceholder": "Please enter contact information (Markdown/HTML supported)" + "contactPlaceholder": "Please enter contact information (Markdown/HTML supported)", + "common": "General Settings", + "article": "Batch Post Generation Feature Grouping", + "articleTip": "Batch post generation function grouping, after checking the current user group can use batch post generation function", + "generate": "AI Project Builder Grouping", + "generateTip": "AI project generator grouping, after checking the current user group can use AI project generator", + "groupPlaceholder": "{{length}} groups selected" }, "user": "User Management", "invitation-code": "Invitation Code", diff --git a/app/src/resources/i18n/ja.json b/app/src/resources/i18n/ja.json index d41ab06..724a8fb 100644 --- a/app/src/resources/i18n/ja.json +++ b/app/src/resources/i18n/ja.json @@ -422,7 +422,8 @@ "normal": "一般ユーザー", "basic": "ベーシックサブスクライバー", "standard": "標準サブスクライバー", - "pro": "Pro Subscribers" + "pro": "Pro Subscribers", + "admin": "管理者ユーザー" }, "joint": "上流にドッキング", "joint-endpoint": "アップストリームアドレス", @@ -509,7 +510,13 @@ "buyLinkPlaceholder": "カードシークレット購入リンクを入力してください。購入ボタンを表示しない場合は空白のままにしてください", "mailConfNotValid": "SMTP送信パラメータが正しく設定されていません。メールボックスの検証が無効になっています", "contact": "コンタクト&インフォメーション", - "contactPlaceholder": "連絡先情報を入力してください( Markdown/HTML対応)" + "contactPlaceholder": "連絡先情報を入力してください( Markdown/HTML対応)", + "common": "基本設定", + "article": "一括ポストジェネレーションフィーチャーグループ", + "articleTip": "バッチポストジェネレーション機能のグループ化、現在のユーザーグループを確認した後、バッチポストジェネレーション機能を使用することができます", + "generate": "AIプロジェクトビルダーグループ", + "generateTip": "AIプロジェクトジェネレータグループ、現在のユーザーグループを確認した後、AIプロジェクトジェネレータを使用することができます", + "groupPlaceholder": "{{length}}グループが選択されました" }, "user": "ユーザー管理", "invitation-code": "招待コード", diff --git a/app/src/resources/i18n/ru.json b/app/src/resources/i18n/ru.json index d8edbbc..e636e9f 100644 --- a/app/src/resources/i18n/ru.json +++ b/app/src/resources/i18n/ru.json @@ -422,7 +422,8 @@ "normal": "обычный пользователь", "basic": "Базовые подписчики", "standard": "Стандартные подписчики", - "pro": "Подписчики Pro" + "pro": "Подписчики Pro", + "admin": "Пользователь-администратор" }, "joint": "Док-станция выше по течению", "joint-endpoint": "Адрес выше по потоку", @@ -509,7 +510,13 @@ "buyLinkPlaceholder": "Введите ссылку на секретную покупку карты, оставьте поле пустым, чтобы не показывать кнопку покупки", "mailConfNotValid": "Параметры отправки SMTP настроены неправильно, проверка почтового ящика отключена", "contact": "shops|Контактные данные", - "contactPlaceholder": "Введите контактную информацию (поддерживается Markdown/HTML)" + "contactPlaceholder": "Введите контактную информацию (поддерживается Markdown/HTML)", + "common": "Основные параметры", + "article": "Группировка функций генерации пакетной записи", + "articleTip": "Группировка функций пакетного пост-генерации, после проверки текущей группы пользователей можно использовать функцию пакетного пост-генерации", + "generate": "Группировка конструкторов ИИ-проектов", + "generateTip": "Группировка генераторов ИИ-проектов, после проверки текущей группы пользователей можно использовать генератор ИИ-проектов", + "groupPlaceholder": "Выбрано групп: {{length}}" }, "user": "Управление пользователями", "invitation-code": "Код приглашения", diff --git a/app/src/routes/admin/System.tsx b/app/src/routes/admin/System.tsx index d74e338..1942473 100644 --- a/app/src/routes/admin/System.tsx +++ b/app/src/routes/admin/System.tsx @@ -18,6 +18,7 @@ import { useMemo, useReducer, useState } from "react"; import { formReducer } from "@/utils/form.ts"; import { NumberInput } from "@/components/ui/number-input.tsx"; import { + CommonState, commonWhiteList, GeneralState, getConfig, @@ -49,6 +50,7 @@ import Tips from "@/components/Tips.tsx"; import { cn } from "@/components/ui/lib/utils.ts"; import { Switch } from "@/components/ui/switch.tsx"; import { MultiCombobox } from "@/components/ui/multi-combobox.tsx"; +import { allGroups } from "@/utils/groups.ts"; type CompProps = { data: T; @@ -464,12 +466,9 @@ function Site({ data, dispatch, onChange }: CompProps) { isCollapsed={true} > -