├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── adapter ├── adapter.go ├── azure │ ├── chat.go │ ├── image.go │ ├── processor.go │ ├── struct.go │ └── types.go ├── baichuan │ ├── chat.go │ ├── processor.go │ ├── struct.go │ └── types.go ├── bing │ ├── chat.go │ ├── struct.go │ └── types.go ├── chatgpt │ ├── chat.go │ ├── image.go │ ├── processor.go │ ├── struct.go │ ├── test.go │ └── types.go ├── claude │ ├── chat.go │ ├── struct.go │ └── types.go ├── dashscope │ ├── chat.go │ ├── struct.go │ └── types.go ├── hunyuan │ ├── chat.go │ ├── sdk.go │ └── struct.go ├── midjourney │ ├── api.go │ ├── chat.go │ ├── expose.go │ ├── storage.go │ ├── struct.go │ └── types.go ├── oneapi │ ├── chat.go │ ├── processor.go │ ├── struct.go │ └── types.go ├── palm2 │ ├── chat.go │ ├── formatter.go │ ├── struct.go │ └── types.go ├── request.go ├── router.go ├── skylark │ ├── chat.go │ ├── formatter.go │ └── struct.go ├── slack │ ├── chat.go │ └── struct.go ├── sparkdesk │ ├── chat.go │ ├── struct.go │ └── types.go ├── zhinao │ ├── chat.go │ ├── processor.go │ ├── struct.go │ └── types.go └── zhipuai │ ├── chat.go │ ├── struct.go │ └── types.go ├── addition ├── article │ ├── api.go │ ├── data │ │ └── .gitkeep │ ├── generate.go │ ├── template.docx │ └── utils.go ├── card │ ├── .gitignore │ ├── card.go │ ├── card.php │ ├── error.php │ ├── favicon.ico │ └── utils.php ├── generation │ ├── api.go │ ├── build.go │ ├── data │ │ └── .gitkeep │ ├── generate.go │ └── prompt.go ├── router.go └── web │ ├── call.go │ ├── duckduckgo.go │ ├── parser.go │ ├── search.go │ ├── utils.go │ └── webpilot.go ├── admin ├── analysis.go ├── controller.go ├── format.go ├── instance.go ├── invitation.go ├── logger.go ├── market.go ├── redeem.go ├── router.go ├── statistic.go ├── types.go └── user.go ├── app ├── .env.deeptrain ├── .eslintrc.cjs ├── .gitignore ├── .prettierrc.json ├── components.json ├── index.html ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public │ ├── favicon.ico │ ├── icons │ │ ├── 360gpt.png │ │ ├── baichuan.png │ │ ├── chatglm.png │ │ ├── claude.png │ │ ├── claude100k.png │ │ ├── dalle.jpeg │ │ ├── gemini.jpeg │ │ ├── gpt35turbo.png │ │ ├── gpt35turbo16k.webp │ │ ├── gpt4.png │ │ ├── gpt432k.webp │ │ ├── gpt4dalle.png │ │ ├── gpt4v.png │ │ ├── hunyuan.png │ │ ├── llama2.webp │ │ ├── llamacode.webp │ │ ├── midjourney.jpg │ │ ├── newbing.jpg │ │ ├── palm2.webp │ │ ├── skylark.jpg │ │ ├── sparkdesk.jpg │ │ ├── stablediffusion.jpeg │ │ └── tongyi.png │ ├── logo.png │ ├── robots.txt │ ├── service.js │ ├── service │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ └── favicon-64x64.png │ ├── site.webmanifest │ ├── source │ │ └── qq.jpg │ └── workbox.js ├── qodana.yaml ├── src-tauri │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ ├── icons │ │ ├── 128x128.png │ │ ├── 128x128@2x.png │ │ ├── 32x32.png │ │ ├── Square107x107Logo.png │ │ ├── Square142x142Logo.png │ │ ├── Square150x150Logo.png │ │ ├── Square284x284Logo.png │ │ ├── Square30x30Logo.png │ │ ├── Square310x310Logo.png │ │ ├── Square44x44Logo.png │ │ ├── Square71x71Logo.png │ │ ├── Square89x89Logo.png │ │ ├── StoreLogo.png │ │ ├── icon.icns │ │ ├── icon.ico │ │ └── icon.png │ ├── src │ │ └── main.rs │ └── tauri.conf.json ├── src │ ├── App.tsx │ ├── admin │ │ ├── api │ │ │ ├── channel.ts │ │ │ ├── charge.ts │ │ │ ├── chart.ts │ │ │ ├── info.ts │ │ │ ├── logger.ts │ │ │ ├── market.ts │ │ │ ├── plan.ts │ │ │ └── system.ts │ │ ├── channel.ts │ │ ├── charge.ts │ │ ├── colors.ts │ │ ├── market.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── api │ │ ├── addition.ts │ │ ├── auth.ts │ │ ├── broadcast.ts │ │ ├── connection.ts │ │ ├── conversation.ts │ │ ├── file.ts │ │ ├── generation.ts │ │ ├── history.ts │ │ ├── invitation.ts │ │ ├── manager.ts │ │ ├── quota.ts │ │ ├── redeem.ts │ │ ├── sharing.ts │ │ ├── types.ts │ │ └── v1.ts │ ├── assets │ │ ├── admin │ │ │ ├── all.less │ │ │ ├── broadcast.less │ │ │ ├── channel.less │ │ │ ├── charge.less │ │ │ ├── dashboard.less │ │ │ ├── logger.less │ │ │ ├── management.less │ │ │ ├── market.less │ │ │ ├── menu.less │ │ │ ├── subscription.less │ │ │ └── system.less │ │ ├── common │ │ │ ├── 404.less │ │ │ ├── editor.less │ │ │ ├── file.less │ │ │ └── loader.less │ │ ├── globals.less │ │ ├── main.less │ │ ├── markdown │ │ │ ├── all.less │ │ │ ├── highlight.less │ │ │ ├── style.less │ │ │ └── theme.less │ │ ├── pages │ │ │ ├── api.less │ │ │ ├── article.less │ │ │ ├── auth.less │ │ │ ├── chat.less │ │ │ ├── generation.less │ │ │ ├── home.less │ │ │ ├── mask.less │ │ │ ├── navbar.less │ │ │ ├── package.less │ │ │ ├── quota.less │ │ │ ├── settings.less │ │ │ ├── share-manager.less │ │ │ ├── sharing.less │ │ │ └── subscription.less │ │ └── ui.less │ ├── components │ │ ├── Avatar.tsx │ │ ├── Broadcast.tsx │ │ ├── EditorProvider.tsx │ │ ├── ErrorBoundary.tsx │ │ ├── FileProvider.tsx │ │ ├── I18nProvider.tsx │ │ ├── Loader.tsx │ │ ├── Markdown.tsx │ │ ├── Message.tsx │ │ ├── OperationAction.tsx │ │ ├── Paragraph.tsx │ │ ├── PopupDialog.tsx │ │ ├── ProjectLink.tsx │ │ ├── ReloadService.tsx │ │ ├── Require.tsx │ │ ├── SelectGroup.tsx │ │ ├── ThemeProvider.tsx │ │ ├── TickButton.tsx │ │ ├── Tips.tsx │ │ ├── admin │ │ │ ├── ChannelSettings.tsx │ │ │ ├── ChargeWidget.tsx │ │ │ ├── ChartBox.tsx │ │ │ ├── InfoBox.tsx │ │ │ ├── InvitationTable.tsx │ │ │ ├── MenuBar.tsx │ │ │ ├── RedeemTable.tsx │ │ │ ├── UserTable.tsx │ │ │ └── assemblies │ │ │ │ ├── BillingChart.tsx │ │ │ │ ├── BroadcastTable.tsx │ │ │ │ ├── ChannelEditor.tsx │ │ │ │ ├── ChannelTable.tsx │ │ │ │ ├── ErrorChart.tsx │ │ │ │ ├── ModelChart.tsx │ │ │ │ ├── ModelUsageChart.tsx │ │ │ │ ├── RequestChart.tsx │ │ │ │ └── UserTypeChart.tsx │ │ ├── app │ │ │ ├── Announcement.tsx │ │ │ ├── AppProvider.tsx │ │ │ ├── MenuBar.tsx │ │ │ └── NavBar.tsx │ │ ├── home │ │ │ ├── ChatInterface.tsx │ │ │ ├── ChatSpace.tsx │ │ │ ├── ChatWrapper.tsx │ │ │ ├── ConversationSegment.tsx │ │ │ ├── ModelFinder.tsx │ │ │ ├── ModelMarket.tsx │ │ │ ├── SideBar.tsx │ │ │ ├── assemblies │ │ │ │ ├── ActionButton.tsx │ │ │ │ ├── ChatAction.tsx │ │ │ │ ├── ChatInput.tsx │ │ │ │ └── ScrollAction.tsx │ │ │ └── subscription │ │ │ │ ├── BuyDialog.tsx │ │ │ │ └── SubscriptionUsage.tsx │ │ ├── plugins │ │ │ ├── file.tsx │ │ │ └── progress.tsx │ │ ├── ui │ │ │ ├── alert-dialog.tsx │ │ │ ├── alert.tsx │ │ │ ├── badge.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── combo-box.tsx │ │ │ ├── command.tsx │ │ │ ├── context-menu.tsx │ │ │ ├── dialog.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── icons │ │ │ │ └── Github.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── lib │ │ │ │ └── utils.ts │ │ │ ├── multi-combobox.tsx │ │ │ ├── number-input.tsx │ │ │ ├── popover.tsx │ │ │ ├── progress.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── sonner.tsx │ │ │ ├── switch.tsx │ │ │ ├── table.tsx │ │ │ ├── tabs.tsx │ │ │ ├── textarea.tsx │ │ │ ├── toast.tsx │ │ │ ├── toaster.tsx │ │ │ ├── toggle-group.tsx │ │ │ ├── toggle.tsx │ │ │ ├── tooltip.tsx │ │ │ └── use-toast.ts │ │ └── utils │ │ │ └── Icon.tsx │ ├── conf │ │ ├── api.ts │ │ ├── deeptrain.ts │ │ ├── env.ts │ │ ├── index.ts │ │ ├── model.ts │ │ ├── storage.ts │ │ └── subscription.tsx │ ├── dialogs │ │ ├── ApikeyDialog.tsx │ │ ├── InvitationDialog.tsx │ │ ├── MaskDialog.tsx │ │ ├── PackageDialog.tsx │ │ ├── QuotaDialog.tsx │ │ ├── SettingsDialog.tsx │ │ ├── ShareManagementDialog.tsx │ │ ├── SubscriptionDialog.tsx │ │ └── index.tsx │ ├── events │ │ ├── announcement.ts │ │ ├── chat.ts │ │ ├── connection.ts │ │ ├── market.ts │ │ ├── mask.ts │ │ ├── model.ts │ │ ├── sharing.ts │ │ ├── spinner.ts │ │ ├── struct.ts │ │ └── theme.ts │ ├── i18n.ts │ ├── main.tsx │ ├── masks │ │ ├── prompts.ts │ │ └── types.ts │ ├── resources │ │ └── i18n │ │ │ ├── cn.json │ │ │ ├── en.json │ │ │ ├── ja.json │ │ │ └── ru.json │ ├── router.tsx │ ├── routes │ │ ├── Admin.tsx │ │ ├── Article.tsx │ │ ├── Auth.tsx │ │ ├── Forgot.tsx │ │ ├── Generation.tsx │ │ ├── Home.tsx │ │ ├── NotFound.tsx │ │ ├── Register.tsx │ │ ├── Sharing.tsx │ │ └── admin │ │ │ ├── Broadcast.tsx │ │ │ ├── Channel.tsx │ │ │ ├── Charge.tsx │ │ │ ├── DashBoard.tsx │ │ │ ├── Logger.tsx │ │ │ ├── Market.tsx │ │ │ ├── Subscription.tsx │ │ │ ├── System.tsx │ │ │ └── Users.tsx │ ├── spinner.tsx │ ├── store │ │ ├── api.ts │ │ ├── auth.ts │ │ ├── chat.ts │ │ ├── globals.ts │ │ ├── index.ts │ │ ├── invitation.ts │ │ ├── menu.ts │ │ ├── package.ts │ │ ├── quota.ts │ │ ├── settings.ts │ │ ├── sharing.ts │ │ ├── subscription.ts │ │ └── utils.ts │ ├── translator │ │ ├── adapter.ts │ │ ├── index.ts │ │ ├── io.ts │ │ └── translator.ts │ ├── types │ │ ├── performance.d.ts │ │ ├── service.d.ts │ │ └── ui.d.ts │ ├── utils │ │ ├── app.ts │ │ ├── base.ts │ │ ├── dev.ts │ │ ├── device.ts │ │ ├── dom.ts │ │ ├── form.ts │ │ ├── hook.ts │ │ ├── loader.tsx │ │ ├── memory.ts │ │ ├── path.ts │ │ └── processor.ts │ └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── auth ├── analysis.go ├── apikey.go ├── auth.go ├── call.go ├── cert.go ├── controller.go ├── invitation.go ├── package.go ├── payment.go ├── quota.go ├── redeem.go ├── router.go ├── rule.go ├── struct.go ├── subscription.go ├── usage.go └── validators.go ├── channel ├── channel.go ├── charge.go ├── controller.go ├── manager.go ├── plan.go ├── router.go ├── sequence.go ├── system.go ├── ticker.go ├── types.go └── worker.go ├── cli ├── admin.go ├── exec.go ├── filter.go ├── help.go ├── invite.go ├── parser.go └── token.go ├── config.example.yaml ├── connection ├── cache.go ├── database.go └── worker.go ├── docker-compose.yaml ├── globals ├── constant.go ├── interface.go ├── logger.go ├── tools.go ├── types.go ├── usage.go └── variables.go ├── go.mod ├── go.sum ├── main.go ├── manager ├── broadcast │ ├── controller.go │ ├── manage.go │ ├── router.go │ ├── types.go │ └── view.go ├── cache.go ├── chat.go ├── chat_completions.go ├── completions.go ├── connection.go ├── conversation │ ├── api.go │ ├── conversation.go │ ├── mask.go │ ├── router.go │ ├── shared.go │ └── storage.go ├── images.go ├── manager.go ├── relay.go ├── router.go ├── types.go └── usage.go ├── middleware ├── auth.go ├── builtins.go ├── cors.go ├── middleware.go └── throttle.go ├── migration ├── 3.6.sql └── 3.8.sql ├── nginx.conf ├── screenshot ├── admin.png ├── channel.png ├── charge.png ├── code.png ├── generation.png ├── landspace.png ├── latex.jpg ├── shop.png └── subscription.png └── utils ├── base.go ├── buffer.go ├── cache.go ├── char.go ├── compress.go ├── config.go ├── ctx.go ├── encrypt.go ├── fs.go ├── image.go ├── net.go ├── smtp.go ├── sse.go ├── templates └── code.html ├── tokenizer.go └── websocket.go /.dockerignore: -------------------------------------------------------------------------------- 1 | app/node_modules 2 | app/src-tauri 3 | app/.idea 4 | app/.vscode 5 | app/dist 6 | app/dev-dist 7 | app/dist-ssr 8 | app/target 9 | app/tauri.conf.json 10 | app/tauri.js 11 | 12 | .vscode 13 | .idea 14 | config.yaml 15 | config.dev.yaml 16 | 17 | addition/generation/data/* 18 | !addition/generation/data/.gitkeep 19 | 20 | addition/article/data/* 21 | !addition/article/data/.gitkeep 22 | sdk 23 | logs 24 | 25 | chat 26 | chat.exe 27 | 28 | # for reverse engine 29 | reverse 30 | access.json 31 | access/*.json 32 | 33 | db 34 | cache 35 | config 36 | 37 | README.md 38 | .gitignore 39 | screenshot 40 | LICENSE 41 | 42 | .github 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | app/node_modules 2 | .vscode 3 | .idea 4 | config.yaml 5 | config.dev.yaml 6 | 7 | addition/generation/data/* 8 | !addition/generation/data/.gitkeep 9 | 10 | addition/article/data/* 11 | !addition/article/data/.gitkeep 12 | sdk 13 | logs 14 | 15 | chat 16 | chat.exe 17 | 18 | # for reverse engine 19 | reverse 20 | access.json 21 | access/*.json 22 | 23 | db 24 | redis 25 | config 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Author: ProgramZmh 2 | # License: Apache-2.0 3 | # Description: Dockerfile for chatnio 4 | 5 | FROM golang:1.20-alpine AS backend 6 | 7 | WORKDIR /backend 8 | COPY . . 9 | 10 | # Set go proxy to https://goproxy.cn (open for vps in China Mainland) 11 | # RUN go env -w GOPROXY=https://goproxy.cn,direct 12 | ENV GOOS=linux GO111MODULE=on CGO_ENABLED=1 13 | 14 | # Install dependencies for cgo 15 | RUN apk add --no-cache gcc musl-dev 16 | 17 | # Build backend 18 | RUN go install && \ 19 | go build . 20 | 21 | FROM node:18 AS frontend 22 | 23 | WORKDIR /app 24 | COPY ./app . 25 | 26 | RUN npm install -g pnpm && \ 27 | pnpm install && \ 28 | pnpm run build && \ 29 | rm -rf node_modules src 30 | 31 | 32 | FROM alpine 33 | 34 | # Install dependencies 35 | RUN apk update && \ 36 | apk upgrade && \ 37 | apk add --no-cache wget ca-certificates tzdata && \ 38 | update-ca-certificates 2>/dev/null || true && \ 39 | rm -rf /var/cache/apk/* 40 | 41 | # Set timezone 42 | RUN echo "Asia/Shanghai" > /etc/timezone && \ 43 | ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 44 | 45 | WORKDIR / 46 | 47 | # Copy dist 48 | COPY --from=backend /backend / 49 | COPY --from=frontend /app/dist /app/dist 50 | 51 | # Expose port 52 | EXPOSE 8094 53 | 54 | # Run application 55 | CMD ["./chat"] 56 | -------------------------------------------------------------------------------- /adapter/azure/struct.go: -------------------------------------------------------------------------------- 1 | package azure 2 | 3 | import ( 4 | "chat/globals" 5 | ) 6 | 7 | type ChatInstance struct { 8 | Endpoint string 9 | ApiKey string 10 | Resource string 11 | } 12 | 13 | type InstanceProps struct { 14 | Model string 15 | Plan bool 16 | } 17 | 18 | func (c *ChatInstance) GetEndpoint() string { 19 | return c.Endpoint 20 | } 21 | 22 | func (c *ChatInstance) GetApiKey() string { 23 | return c.ApiKey 24 | } 25 | 26 | func (c *ChatInstance) GetResource() string { 27 | return c.Resource 28 | } 29 | 30 | func (c *ChatInstance) GetHeader() map[string]string { 31 | return map[string]string{ 32 | "Content-Type": "application/json", 33 | "api-key": c.GetApiKey(), 34 | } 35 | } 36 | 37 | func NewChatInstance(endpoint, apiKey string, resource string) *ChatInstance { 38 | return &ChatInstance{ 39 | Endpoint: endpoint, 40 | ApiKey: apiKey, 41 | Resource: resource, 42 | } 43 | } 44 | 45 | func NewChatInstanceFromConfig(conf globals.ChannelConfig) *ChatInstance { 46 | param := conf.SplitRandomSecret(2) 47 | return NewChatInstance( 48 | conf.GetEndpoint(), 49 | param[0], 50 | param[1], 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /adapter/baichuan/struct.go: -------------------------------------------------------------------------------- 1 | package baichuan 2 | 3 | import ( 4 | "chat/globals" 5 | "fmt" 6 | ) 7 | 8 | type ChatInstance struct { 9 | Endpoint string 10 | ApiKey string 11 | } 12 | 13 | func (c *ChatInstance) GetEndpoint() string { 14 | return c.Endpoint 15 | } 16 | 17 | func (c *ChatInstance) GetApiKey() string { 18 | return c.ApiKey 19 | } 20 | 21 | func (c *ChatInstance) GetHeader() map[string]string { 22 | return map[string]string{ 23 | "Content-Type": "application/json", 24 | "Authorization": fmt.Sprintf("Bearer %s", c.GetApiKey()), 25 | } 26 | } 27 | 28 | func NewChatInstance(endpoint, apiKey string) *ChatInstance { 29 | return &ChatInstance{ 30 | Endpoint: endpoint, 31 | ApiKey: apiKey, 32 | } 33 | } 34 | 35 | func NewChatInstanceFromConfig(conf globals.ChannelConfig) *ChatInstance { 36 | return NewChatInstance( 37 | conf.GetEndpoint(), 38 | conf.GetRandomSecret(), 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /adapter/bing/chat.go: -------------------------------------------------------------------------------- 1 | package bing 2 | 3 | import ( 4 | "chat/globals" 5 | "chat/utils" 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | type ChatProps struct { 11 | Message []globals.Message 12 | Model string 13 | } 14 | 15 | func (c *ChatInstance) CreateStreamChatRequest(props *ChatProps, hook globals.Hook) error { 16 | var conn *utils.WebSocket 17 | if conn = utils.NewWebsocketClient(c.GetEndpoint()); conn == nil { 18 | return fmt.Errorf("bing error: websocket connection failed") 19 | } 20 | defer conn.DeferClose() 21 | 22 | model := strings.TrimPrefix(props.Model, "bing-") 23 | prompt := props.Message[len(props.Message)-1].Content 24 | if err := conn.SendJSON(&ChatRequest{ 25 | Prompt: prompt, 26 | Hash: c.Secret, 27 | Model: model, 28 | }); err != nil { 29 | return err 30 | } 31 | 32 | for { 33 | form := utils.ReadForm[ChatResponse](conn) 34 | if form == nil { 35 | return nil 36 | } 37 | 38 | if err := hook(form.Response); err != nil { 39 | return err 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /adapter/bing/struct.go: -------------------------------------------------------------------------------- 1 | package bing 2 | 3 | import ( 4 | "chat/globals" 5 | "fmt" 6 | ) 7 | 8 | type ChatInstance struct { 9 | Endpoint string 10 | Secret string 11 | } 12 | 13 | func (c *ChatInstance) GetEndpoint() string { 14 | return fmt.Sprintf("%s/chat", c.Endpoint) 15 | } 16 | 17 | func NewChatInstance(endpoint, secret string) *ChatInstance { 18 | return &ChatInstance{ 19 | Endpoint: endpoint, 20 | Secret: secret, 21 | } 22 | } 23 | 24 | func NewChatInstanceFromConfig(conf globals.ChannelConfig) *ChatInstance { 25 | return NewChatInstance( 26 | conf.GetEndpoint(), 27 | conf.GetRandomSecret(), 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /adapter/bing/types.go: -------------------------------------------------------------------------------- 1 | package bing 2 | 3 | // see https://github.com/Deeptrain-Community/chatnio-bing-service 4 | 5 | type ChatRequest struct { 6 | Prompt string `json:"prompt"` 7 | Hash string `json:"hash"` 8 | Model string `json:"model"` 9 | } 10 | 11 | type ChatResponse struct { 12 | Response string `json:"response"` 13 | } 14 | -------------------------------------------------------------------------------- /adapter/chatgpt/image.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "chat/globals" 5 | "chat/utils" 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | type ImageProps struct { 11 | Model string 12 | Prompt string 13 | Size ImageSize 14 | } 15 | 16 | func (c *ChatInstance) GetImageEndpoint() string { 17 | return fmt.Sprintf("%s/v1/images/generations", c.GetEndpoint()) 18 | } 19 | 20 | // CreateImageRequest will create a dalle image from prompt, return url of image and error 21 | func (c *ChatInstance) CreateImageRequest(props ImageProps) (string, error) { 22 | res, err := utils.Post( 23 | c.GetImageEndpoint(), 24 | c.GetHeader(), ImageRequest{ 25 | Model: props.Model, 26 | Prompt: props.Prompt, 27 | Size: utils.Multi[ImageSize]( 28 | props.Model == globals.Dalle3, 29 | ImageSize1024, 30 | ImageSize512, 31 | ), 32 | N: 1, 33 | }) 34 | if err != nil || res == nil { 35 | return "", fmt.Errorf("chatgpt error: %s", err.Error()) 36 | } 37 | 38 | data := utils.MapToStruct[ImageResponse](res) 39 | if data == nil { 40 | return "", fmt.Errorf("chatgpt error: cannot parse response") 41 | } else if data.Error.Message != "" { 42 | return "", fmt.Errorf("chatgpt error: %s", data.Error.Message) 43 | } 44 | 45 | return data.Data[0].Url, nil 46 | } 47 | 48 | // CreateImage will create a dalle image from prompt, return markdown of image 49 | func (c *ChatInstance) CreateImage(props *ChatProps) (string, error) { 50 | url, err := c.CreateImageRequest(ImageProps{ 51 | Model: props.Model, 52 | Prompt: c.GetLatestPrompt(props), 53 | }) 54 | if err != nil { 55 | if strings.Contains(err.Error(), "safety") { 56 | return err.Error(), nil 57 | } 58 | return "", err 59 | } 60 | 61 | return utils.GetImageMarkdown(url), nil 62 | } 63 | -------------------------------------------------------------------------------- /adapter/chatgpt/struct.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "chat/globals" 5 | "fmt" 6 | ) 7 | 8 | type ChatInstance struct { 9 | Endpoint string 10 | ApiKey string 11 | } 12 | 13 | type InstanceProps struct { 14 | Model string 15 | Plan bool 16 | } 17 | 18 | func (c *ChatInstance) GetEndpoint() string { 19 | return c.Endpoint 20 | } 21 | 22 | func (c *ChatInstance) GetApiKey() string { 23 | return c.ApiKey 24 | } 25 | 26 | func (c *ChatInstance) GetHeader() map[string]string { 27 | return map[string]string{ 28 | "Content-Type": "application/json", 29 | "Authorization": fmt.Sprintf("Bearer %s", c.GetApiKey()), 30 | } 31 | } 32 | 33 | func NewChatInstance(endpoint, apiKey string) *ChatInstance { 34 | return &ChatInstance{ 35 | Endpoint: endpoint, 36 | ApiKey: apiKey, 37 | } 38 | } 39 | 40 | func NewChatInstanceFromConfig(conf globals.ChannelConfig) *ChatInstance { 41 | return NewChatInstance( 42 | conf.GetEndpoint(), 43 | conf.GetRandomSecret(), 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /adapter/chatgpt/test.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "chat/globals" 5 | "chat/utils" 6 | "fmt" 7 | "github.com/spf13/viper" 8 | "strings" 9 | ) 10 | 11 | func (c *ChatInstance) Test() bool { 12 | result, err := c.CreateChatRequest(&ChatProps{ 13 | Model: globals.GPT3Turbo, 14 | Message: []globals.Message{{Role: globals.User, Content: "hi"}}, 15 | Token: utils.ToPtr(1), 16 | }) 17 | if err != nil { 18 | fmt.Println(fmt.Sprintf("%s: test failed (%s)", c.GetApiKey(), err.Error())) 19 | } 20 | 21 | return err == nil && len(result) > 0 22 | } 23 | 24 | func FilterKeys(v string) []string { 25 | endpoint := viper.GetString(fmt.Sprintf("openai.%s.endpoint", v)) 26 | keys := strings.Split(viper.GetString(fmt.Sprintf("openai.%s.apikey", v)), "|") 27 | 28 | return FilterKeysNative(endpoint, keys) 29 | } 30 | 31 | func FilterKeysNative(endpoint string, keys []string) []string { 32 | stack := make(chan string, len(keys)) 33 | for _, key := range keys { 34 | go func(key string) { 35 | instance := NewChatInstance(endpoint, key) 36 | stack <- utils.Multi[string](instance.Test(), key, "") 37 | }(key) 38 | } 39 | 40 | var result []string 41 | for i := 0; i < len(keys); i++ { 42 | if res := <-stack; res != "" { 43 | result = append(result, res) 44 | } 45 | } 46 | return result 47 | } 48 | -------------------------------------------------------------------------------- /adapter/claude/struct.go: -------------------------------------------------------------------------------- 1 | package claude 2 | 3 | import ( 4 | "chat/globals" 5 | ) 6 | 7 | type ChatInstance struct { 8 | Endpoint string 9 | ApiKey string 10 | } 11 | 12 | func NewChatInstance(endpoint, apiKey string) *ChatInstance { 13 | return &ChatInstance{ 14 | Endpoint: endpoint, 15 | ApiKey: apiKey, 16 | } 17 | } 18 | 19 | func NewChatInstanceFromConfig(conf globals.ChannelConfig) *ChatInstance { 20 | return NewChatInstance( 21 | conf.GetEndpoint(), 22 | conf.GetRandomSecret(), 23 | ) 24 | } 25 | 26 | func (c *ChatInstance) GetEndpoint() string { 27 | return c.Endpoint 28 | } 29 | 30 | func (c *ChatInstance) GetApiKey() string { 31 | return c.ApiKey 32 | } 33 | -------------------------------------------------------------------------------- /adapter/claude/types.go: -------------------------------------------------------------------------------- 1 | package claude 2 | 3 | // ChatBody is the request body for anthropic claude 4 | type ChatBody struct { 5 | Prompt string `json:"prompt"` 6 | MaxTokensToSample int `json:"max_tokens_to_sample"` 7 | Model string `json:"model"` 8 | Stream bool `json:"stream"` 9 | Temperature *float32 `json:"temperature,omitempty"` 10 | TopP *float32 `json:"top_p,omitempty"` 11 | TopK *int `json:"top_k,omitempty"` 12 | } 13 | 14 | // ChatResponse is the native http request and stream response for anthropic claude 15 | type ChatResponse struct { 16 | Completion string `json:"completion"` 17 | LogId string `json:"log_id"` 18 | } 19 | -------------------------------------------------------------------------------- /adapter/dashscope/struct.go: -------------------------------------------------------------------------------- 1 | package dashscope 2 | 3 | import ( 4 | "chat/globals" 5 | ) 6 | 7 | type ChatInstance struct { 8 | Endpoint string 9 | ApiKey string 10 | } 11 | 12 | func (c *ChatInstance) GetApiKey() string { 13 | return c.ApiKey 14 | } 15 | 16 | func (c *ChatInstance) GetEndpoint() string { 17 | return c.Endpoint 18 | } 19 | 20 | func NewChatInstance(endpoint string, apiKey string) *ChatInstance { 21 | return &ChatInstance{ 22 | Endpoint: endpoint, 23 | ApiKey: apiKey, 24 | } 25 | } 26 | 27 | func NewChatInstanceFromConfig(conf globals.ChannelConfig) *ChatInstance { 28 | return NewChatInstance( 29 | conf.GetEndpoint(), 30 | conf.GetRandomSecret(), 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /adapter/dashscope/types.go: -------------------------------------------------------------------------------- 1 | package dashscope 2 | 3 | // ChatRequest is the request body for dashscope 4 | type ChatRequest struct { 5 | Model string `json:"model"` 6 | Input ChatInput `json:"input"` 7 | Parameters ChatParam `json:"parameters"` 8 | } 9 | 10 | type Message struct { 11 | Role string `json:"role"` 12 | Content string `json:"content"` 13 | } 14 | 15 | type ChatInput struct { 16 | Messages []Message `json:"messages"` 17 | } 18 | 19 | type ChatParam struct { 20 | IncrementalOutput bool `json:"incremental_output"` 21 | EnableSearch *bool `json:"enable_search,omitempty"` 22 | MaxTokens int `json:"max_tokens"` 23 | Temperature *float32 `json:"temperature,omitempty"` 24 | TopP *float32 `json:"top_p,omitempty"` 25 | TopK *int `json:"top_k,omitempty"` 26 | RepetitionPenalty *float32 `json:"repetition_penalty,omitempty"` 27 | } 28 | 29 | // ChatResponse is the response body for dashscope 30 | type ChatResponse struct { 31 | Output struct { 32 | FinishReason string `json:"finish_reason"` 33 | Text string `json:"text"` 34 | } `json:"output"` 35 | RequestId string `json:"request_id"` 36 | Usage struct { 37 | InputTokens int `json:"input_tokens"` 38 | OutputTokens int `json:"output_tokens"` 39 | } `json:"usage"` 40 | Message string `json:"message"` 41 | } 42 | -------------------------------------------------------------------------------- /adapter/hunyuan/chat.go: -------------------------------------------------------------------------------- 1 | package hunyuan 2 | 3 | import ( 4 | "chat/globals" 5 | "context" 6 | "fmt" 7 | ) 8 | 9 | type ChatProps struct { 10 | Model string 11 | Message []globals.Message 12 | Temperature *float32 13 | TopP *float32 14 | } 15 | 16 | func (c *ChatInstance) FormatMessages(messages []globals.Message) []globals.Message { 17 | var result []globals.Message 18 | for _, message := range messages { 19 | switch message.Role { 20 | case globals.System: 21 | result = append(result, globals.Message{Role: globals.User, Content: message.Content}) 22 | case globals.Assistant, globals.User: 23 | bound := len(result) > 0 && result[len(result)-1].Role == message.Role 24 | if bound { 25 | result[len(result)-1].Content += message.Content 26 | } else { 27 | result = append(result, message) 28 | } 29 | case globals.Tool: 30 | continue 31 | default: 32 | result = append(result, message) 33 | } 34 | } 35 | 36 | return result 37 | } 38 | 39 | func (c *ChatInstance) CreateStreamChatRequest(props *ChatProps, callback globals.Hook) error { 40 | credential := NewCredential(c.GetSecretId(), c.GetSecretKey()) 41 | client := NewInstance(c.GetAppId(), c.GetEndpoint(), credential) 42 | channel, err := client.Chat(context.Background(), NewRequest(Stream, c.FormatMessages(props.Message), props.Temperature, props.TopP)) 43 | if err != nil { 44 | return fmt.Errorf("tencent hunyuan error: %+v", err) 45 | } 46 | 47 | for chunk := range channel { 48 | if chunk.Error.Code != 0 { 49 | fmt.Printf("tencent hunyuan error: %+v\n", chunk.Error) 50 | break 51 | } 52 | 53 | if err := callback(chunk.Choices[0].Delta.Content); err != nil { 54 | return err 55 | } 56 | } 57 | 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /adapter/hunyuan/struct.go: -------------------------------------------------------------------------------- 1 | package hunyuan 2 | 3 | import ( 4 | "chat/globals" 5 | "chat/utils" 6 | ) 7 | 8 | type ChatInstance struct { 9 | Endpoint string 10 | AppId int64 11 | SecretId string 12 | SecretKey string 13 | } 14 | 15 | func (c *ChatInstance) GetAppId() int64 { 16 | return c.AppId 17 | } 18 | 19 | func (c *ChatInstance) GetEndpoint() string { 20 | return c.Endpoint 21 | } 22 | 23 | func (c *ChatInstance) GetSecretId() string { 24 | return c.SecretId 25 | } 26 | 27 | func (c *ChatInstance) GetSecretKey() string { 28 | return c.SecretKey 29 | } 30 | 31 | func NewChatInstance(endpoint, appId, secretId, secretKey string) *ChatInstance { 32 | return &ChatInstance{ 33 | Endpoint: endpoint, 34 | AppId: utils.ParseInt64(appId), 35 | SecretId: secretId, 36 | SecretKey: secretKey, 37 | } 38 | } 39 | 40 | func NewChatInstanceFromConfig(conf globals.ChannelConfig) *ChatInstance { 41 | params := conf.SplitRandomSecret(3) 42 | return NewChatInstance( 43 | conf.GetEndpoint(), 44 | params[0], params[1], params[2], 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /adapter/midjourney/expose.go: -------------------------------------------------------------------------------- 1 | package midjourney 2 | 3 | import ( 4 | "chat/globals" 5 | "chat/utils" 6 | "fmt" 7 | "github.com/gin-gonic/gin" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | var whiteList []string 13 | 14 | func SaveWhiteList(raw string) { 15 | arr := utils.Filter(strings.Split(raw, ","), func(s string) bool { 16 | return len(strings.TrimSpace(s)) > 0 17 | }) 18 | 19 | for _, ip := range arr { 20 | if !utils.Contains(ip, whiteList) { 21 | whiteList = append(whiteList, ip) 22 | } 23 | } 24 | } 25 | 26 | func InWhiteList(ip string) bool { 27 | if len(whiteList) == 0 { 28 | return true 29 | } 30 | return utils.Contains(ip, whiteList) 31 | } 32 | 33 | func NotifyAPI(c *gin.Context) { 34 | if !InWhiteList(c.ClientIP()) { 35 | globals.Info(fmt.Sprintf("[midjourney] notify api: banned request from %s", c.ClientIP())) 36 | c.AbortWithStatus(http.StatusForbidden) 37 | return 38 | } 39 | 40 | var form NotifyForm 41 | if err := c.ShouldBindJSON(&form); err != nil { 42 | c.AbortWithStatus(http.StatusBadRequest) 43 | return 44 | } 45 | globals.Debug(fmt.Sprintf("[midjourney] notify api: get notify: %s (from: %s)", utils.Marshal(form), c.ClientIP())) 46 | 47 | if !utils.Contains(form.Status, []string{InProgress, Success, Failure}) { 48 | // ignore 49 | return 50 | } 51 | 52 | reason, ok := form.FailReason.(string) 53 | if !ok { 54 | reason = "unknown" 55 | } 56 | 57 | err := setStorage(form.Id, StorageForm{ 58 | Url: form.ImageUrl, 59 | FailReason: reason, 60 | Progress: form.Progress, 61 | Status: form.Status, 62 | }) 63 | 64 | c.JSON(http.StatusOK, gin.H{ 65 | "status": err == nil, 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /adapter/midjourney/storage.go: -------------------------------------------------------------------------------- 1 | package midjourney 2 | 3 | import ( 4 | "chat/connection" 5 | "chat/utils" 6 | "fmt" 7 | ) 8 | 9 | func getTaskName(task string) string { 10 | return fmt.Sprintf("nio:mj-task:%s", task) 11 | } 12 | 13 | func setStorage(task string, form StorageForm) error { 14 | return utils.SetJson(connection.Cache, getTaskName(task), form, 60*60) 15 | } 16 | 17 | func getStorage(task string) *StorageForm { 18 | return utils.GetJson[StorageForm](connection.Cache, getTaskName(task)) 19 | } 20 | -------------------------------------------------------------------------------- /adapter/midjourney/struct.go: -------------------------------------------------------------------------------- 1 | package midjourney 2 | 3 | import ( 4 | "chat/globals" 5 | ) 6 | 7 | type ChatInstance struct { 8 | Endpoint string 9 | ApiSecret string 10 | } 11 | 12 | func (c *ChatInstance) GetApiSecret() string { 13 | return c.ApiSecret 14 | } 15 | 16 | func (c *ChatInstance) GetEndpoint() string { 17 | return c.Endpoint 18 | } 19 | 20 | func NewChatInstance(endpoint, apiSecret, whiteList string) *ChatInstance { 21 | SaveWhiteList(whiteList) 22 | 23 | return &ChatInstance{ 24 | Endpoint: endpoint, 25 | ApiSecret: apiSecret, 26 | } 27 | } 28 | 29 | func NewChatInstanceFromConfig(conf globals.ChannelConfig) *ChatInstance { 30 | params := conf.SplitRandomSecret(2) 31 | 32 | return NewChatInstance( 33 | conf.GetEndpoint(), 34 | params[0], params[1], 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /adapter/oneapi/struct.go: -------------------------------------------------------------------------------- 1 | package oneapi 2 | 3 | import ( 4 | "chat/globals" 5 | "fmt" 6 | ) 7 | 8 | type ChatInstance struct { 9 | Endpoint string 10 | ApiKey string 11 | } 12 | 13 | type InstanceProps struct { 14 | Model string 15 | Plan bool 16 | } 17 | 18 | func (c *ChatInstance) GetEndpoint() string { 19 | return c.Endpoint 20 | } 21 | 22 | func (c *ChatInstance) GetApiKey() string { 23 | return c.ApiKey 24 | } 25 | 26 | func (c *ChatInstance) GetHeader() map[string]string { 27 | return map[string]string{ 28 | "Content-Type": "application/json", 29 | "Authorization": fmt.Sprintf("Bearer %s", c.GetApiKey()), 30 | } 31 | } 32 | 33 | func NewChatInstance(endpoint, apiKey string) *ChatInstance { 34 | return &ChatInstance{ 35 | Endpoint: endpoint, 36 | ApiKey: apiKey, 37 | } 38 | } 39 | 40 | func NewChatInstanceFromConfig(conf globals.ChannelConfig) *ChatInstance { 41 | return NewChatInstance( 42 | conf.GetEndpoint(), 43 | conf.GetRandomSecret(), 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /adapter/palm2/struct.go: -------------------------------------------------------------------------------- 1 | package palm2 2 | 3 | import ( 4 | "chat/globals" 5 | ) 6 | 7 | type ChatInstance struct { 8 | Endpoint string 9 | ApiKey string 10 | } 11 | 12 | func (c *ChatInstance) GetApiKey() string { 13 | return c.ApiKey 14 | } 15 | 16 | func (c *ChatInstance) GetEndpoint() string { 17 | return c.Endpoint 18 | } 19 | 20 | func NewChatInstance(endpoint string, apiKey string) *ChatInstance { 21 | return &ChatInstance{ 22 | Endpoint: endpoint, 23 | ApiKey: apiKey, 24 | } 25 | } 26 | 27 | func NewChatInstanceFromConfig(conf globals.ChannelConfig) *ChatInstance { 28 | return NewChatInstance( 29 | conf.GetEndpoint(), 30 | conf.GetRandomSecret(), 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /adapter/request.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | import ( 4 | "chat/globals" 5 | "fmt" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | func IsAvailableError(err error) bool { 11 | return err != nil && err.Error() != "signal" 12 | } 13 | 14 | func isQPSOverLimit(model string, err error) bool { 15 | switch model { 16 | case globals.SparkDesk, globals.SparkDeskV2, globals.SparkDeskV3: 17 | return strings.Contains(err.Error(), "AppIdQpsOverFlowError") 18 | default: 19 | return false 20 | } 21 | } 22 | 23 | func NewChatRequest(conf globals.ChannelConfig, props *ChatProps, hook globals.Hook) error { 24 | err := createChatRequest(conf, props, hook) 25 | 26 | retries := conf.GetRetry() 27 | props.Current++ 28 | 29 | if IsAvailableError(err) { 30 | if isQPSOverLimit(props.Model, err) { 31 | // sleep for 0.5s to avoid qps limit 32 | 33 | globals.Info(fmt.Sprintf("qps limit for %s, sleep and retry (times: %d)", props.Model, props.Current)) 34 | time.Sleep(500 * time.Millisecond) 35 | return NewChatRequest(conf, props, hook) 36 | } 37 | 38 | if props.Current < retries { 39 | content := strings.Replace(err.Error(), "\n", "", -1) 40 | globals.Warn(fmt.Sprintf("retrying chat request for %s (attempt %d/%d, error: %s)", props.Model, props.Current+1, retries, content)) 41 | return NewChatRequest(conf, props, hook) 42 | } 43 | } 44 | 45 | return conf.ProcessError(err) 46 | } 47 | -------------------------------------------------------------------------------- /adapter/router.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | import ( 4 | "chat/adapter/midjourney" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func Register(app *gin.RouterGroup) { 9 | app.POST("/mj/notify", midjourney.NotifyAPI) 10 | } 11 | -------------------------------------------------------------------------------- /adapter/skylark/struct.go: -------------------------------------------------------------------------------- 1 | package skylark 2 | 3 | import ( 4 | "chat/globals" 5 | "github.com/volcengine/volc-sdk-golang/service/maas" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | defaultHost = "maas-api.ml-platform-cn-beijing.volces.com" 11 | defaultRegion = "cn-beijing" 12 | ) 13 | 14 | type ChatInstance struct { 15 | Instance *maas.MaaS 16 | } 17 | 18 | func getHost(endpoint string) string { 19 | seg := strings.Split(endpoint, "://") 20 | if len(seg) > 1 && seg[1] != "" { 21 | return seg[1] 22 | } 23 | 24 | return defaultHost 25 | } 26 | 27 | func getRegion(endpoint string) string { 28 | host := getHost(endpoint) 29 | seg := strings.TrimSuffix(strings.TrimPrefix(host, "maas-api.ml-platform-"), ".volces.com") 30 | if seg != "" { 31 | return seg 32 | } 33 | 34 | return defaultRegion 35 | } 36 | 37 | func NewChatInstance(endpoint, accessKey, secretKey string) *ChatInstance { 38 | instance := maas.NewInstance(getHost(endpoint), getRegion(endpoint)) 39 | instance.SetAccessKey(accessKey) 40 | instance.SetSecretKey(secretKey) 41 | return &ChatInstance{ 42 | Instance: instance, 43 | } 44 | } 45 | 46 | func NewChatInstanceFromConfig(conf globals.ChannelConfig) *ChatInstance { 47 | params := conf.SplitRandomSecret(2) 48 | 49 | return NewChatInstance( 50 | conf.GetEndpoint(), 51 | params[0], params[1], 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /adapter/slack/chat.go: -------------------------------------------------------------------------------- 1 | package slack 2 | 3 | import ( 4 | "chat/globals" 5 | "context" 6 | ) 7 | 8 | type ChatProps struct { 9 | Message []globals.Message 10 | } 11 | 12 | func (c *ChatInstance) CreateStreamChatRequest(props *ChatProps, hook globals.Hook) error { 13 | if err := c.Instance.NewChannel(c.GetChannel()); err != nil { 14 | return err 15 | } 16 | 17 | resp, err := c.Instance.Reply(context.Background(), c.FormatMessage(props.Message), nil) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | return c.ProcessPartialResponse(resp, hook) 23 | } 24 | -------------------------------------------------------------------------------- /adapter/zhinao/struct.go: -------------------------------------------------------------------------------- 1 | package zhinao 2 | 3 | import ( 4 | "chat/globals" 5 | "fmt" 6 | ) 7 | 8 | type ChatInstance struct { 9 | Endpoint string 10 | ApiKey string 11 | } 12 | 13 | func (c *ChatInstance) GetEndpoint() string { 14 | return c.Endpoint 15 | } 16 | 17 | func (c *ChatInstance) GetApiKey() string { 18 | return c.ApiKey 19 | } 20 | 21 | func (c *ChatInstance) GetHeader() map[string]string { 22 | return map[string]string{ 23 | "Content-Type": "application/json", 24 | "Authorization": fmt.Sprintf("Bearer %s", c.GetApiKey()), 25 | } 26 | } 27 | 28 | func NewChatInstance(endpoint, apiKey string) *ChatInstance { 29 | return &ChatInstance{ 30 | Endpoint: endpoint, 31 | ApiKey: apiKey, 32 | } 33 | } 34 | 35 | func NewChatInstanceFromConfig(conf globals.ChannelConfig) *ChatInstance { 36 | return NewChatInstance( 37 | conf.GetEndpoint(), 38 | conf.GetRandomSecret(), 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /adapter/zhipuai/struct.go: -------------------------------------------------------------------------------- 1 | package zhipuai 2 | 3 | import ( 4 | "chat/globals" 5 | "chat/utils" 6 | "github.com/dgrijalva/jwt-go" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | type ChatInstance struct { 12 | Endpoint string 13 | ApiKey string 14 | } 15 | 16 | func (c *ChatInstance) GetToken() string { 17 | // get jwt token for zhipuai api 18 | segment := strings.Split(c.ApiKey, ".") 19 | if len(segment) != 2 { 20 | return "" 21 | } 22 | id, secret := segment[0], segment[1] 23 | 24 | payload := utils.MapToStruct[jwt.MapClaims](Payload{ 25 | ApiKey: id, 26 | Exp: time.Now().Add(time.Minute*5).Unix() * 1000, 27 | TimeStamp: time.Now().Unix() * 1000, 28 | }) 29 | 30 | instance := jwt.NewWithClaims(jwt.SigningMethodHS256, payload) 31 | instance.Header = map[string]interface{}{ 32 | "alg": "HS256", 33 | "sign_type": "SIGN", 34 | } 35 | token, _ := instance.SignedString([]byte(secret)) 36 | return token 37 | } 38 | 39 | func (c *ChatInstance) GetEndpoint() string { 40 | return c.Endpoint 41 | } 42 | 43 | func NewChatInstance(endpoint, apikey string) *ChatInstance { 44 | return &ChatInstance{ 45 | Endpoint: endpoint, 46 | ApiKey: apikey, 47 | } 48 | } 49 | 50 | func NewChatInstanceFromConfig(conf globals.ChannelConfig) *ChatInstance { 51 | return NewChatInstance(conf.GetEndpoint(), conf.GetRandomSecret()) 52 | } 53 | -------------------------------------------------------------------------------- /adapter/zhipuai/types.go: -------------------------------------------------------------------------------- 1 | package zhipuai 2 | 3 | import "chat/globals" 4 | 5 | const ( 6 | ChatGLMTurbo = "chatglm_turbo" 7 | ChatGLMPro = "chatglm_pro" 8 | ChatGLMStd = "chatglm_std" 9 | ChatGLMLite = "chatglm_lite" 10 | ) 11 | 12 | type Payload struct { 13 | ApiKey string `json:"api_key"` 14 | Exp int64 `json:"exp"` 15 | TimeStamp int64 `json:"timestamp"` 16 | } 17 | 18 | type ChatRequest struct { 19 | Prompt []globals.Message `json:"prompt"` 20 | Temperature *float32 `json:"temperature,omitempty"` 21 | TopP *float32 `json:"top_p,omitempty"` 22 | Ref *ChatRef `json:"ref,omitempty"` 23 | } 24 | 25 | type ChatRef struct { 26 | Enable *bool `json:"enable,omitempty"` 27 | SearchQuery *string `json:"search_query,omitempty"` 28 | } 29 | 30 | type Occurrence struct { 31 | Code int `json:"code"` 32 | Msg string `json:"msg"` 33 | Success bool `json:"success"` 34 | } 35 | -------------------------------------------------------------------------------- /addition/article/data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/addition/article/data/.gitkeep -------------------------------------------------------------------------------- /addition/article/template.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/addition/article/template.docx -------------------------------------------------------------------------------- /addition/article/utils.go: -------------------------------------------------------------------------------- 1 | package article 2 | 3 | import ( 4 | "chat/globals" 5 | "chat/utils" 6 | "fmt" 7 | "github.com/lukasjarosch/go-docx" 8 | ) 9 | 10 | func GenerateDocxFile(target, title, content string) error { 11 | data := docx.PlaceholderMap{ 12 | "title": title, 13 | "content": content, 14 | } 15 | 16 | doc, err := docx.Open("addition/article/template.docx") 17 | if err != nil { 18 | return err 19 | } 20 | 21 | if err := doc.ReplaceAll(data); err != nil { 22 | return err 23 | } 24 | 25 | if err := doc.WriteToFile(target); err != nil { 26 | return err 27 | } 28 | 29 | return nil 30 | } 31 | 32 | func CreateArticleFile(hash, title, content string) string { 33 | target := fmt.Sprintf("addition/article/data/%s/%s.docx", hash, title) 34 | utils.CreateFolderOnFile(target) 35 | if err := GenerateDocxFile(target, title, content); err != nil { 36 | globals.Debug(fmt.Sprintf("[article] error during generate article %s: %s", title, err.Error())) 37 | } 38 | 39 | return target 40 | } 41 | -------------------------------------------------------------------------------- /addition/card/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | -------------------------------------------------------------------------------- /addition/card/error.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | Error 13 | Error Card 14 | 37 | 38 | 39 | 40 | Sorry, there is something wrong... 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /addition/card/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/addition/card/favicon.ico -------------------------------------------------------------------------------- /addition/card/utils.php: -------------------------------------------------------------------------------- 1 | [^\S ]+/', '/[^\S ]+ ', '<', '\\1', '><', ':', '{', '}'); 9 | return preg_replace($search, $replace, $buffer); 10 | } 11 | 12 | function fetch($message, $web): array|string|null 13 | { 14 | $opts = array('http' => 15 | array( 16 | 'method' => 'POST', 17 | 'header' => 'Content-type: application/json', 18 | 'content' => json_encode(array('message' => $message, 'web' => $web)) 19 | ) 20 | ); 21 | 22 | $context = stream_context_create($opts); 23 | $response = @file_get_contents("http://localhost:8094/card", false, $context); 24 | $ok = $response !== false; 25 | return $ok ? json_decode($response, true) : null; 26 | } 27 | 28 | function get($param, $default = null) 29 | { 30 | return $_GET[$param] ?? $default; 31 | } 32 | -------------------------------------------------------------------------------- /addition/generation/build.go: -------------------------------------------------------------------------------- 1 | package generation 2 | 3 | import ( 4 | "chat/utils" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | func GetFolder(hash string) string { 10 | return fmt.Sprintf("addition/generation/data/%s", hash) 11 | } 12 | 13 | func GetFolderByHash(model string, prompt string) (string, string) { 14 | hash := utils.Sha2Encrypt(model + prompt + time.Now().Format("2006-01-02 15:04:05")) 15 | return hash, GetFolder(hash) 16 | } 17 | 18 | func GenerateProject(path string, instance ProjectResult) bool { 19 | for name, data := range instance.Result { 20 | current := fmt.Sprintf("%s/%s", path, name) 21 | if content, ok := data.(string); ok { 22 | if !utils.WriteFile(current, content, true) { 23 | return false 24 | } 25 | } else { 26 | GenerateProject(current, ProjectResult{ 27 | Result: data.(map[string]interface{}), 28 | }) 29 | } 30 | } 31 | return true 32 | } 33 | -------------------------------------------------------------------------------- /addition/generation/data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/addition/generation/data/.gitkeep -------------------------------------------------------------------------------- /addition/generation/generate.go: -------------------------------------------------------------------------------- 1 | package generation 2 | 3 | import ( 4 | "chat/globals" 5 | "chat/utils" 6 | "fmt" 7 | ) 8 | 9 | func CreateGenerationWithCache(group, model, prompt string, enableReverse bool, hook func(buffer *utils.Buffer, data string)) (string, error) { 10 | hash, path := GetFolderByHash(model, prompt) 11 | if !utils.Exists(path) { 12 | if err := CreateGeneration(group, model, prompt, path, enableReverse, hook); err != nil { 13 | globals.Info(fmt.Sprintf("[project] error during generation %s (model %s): %s", prompt, model, err.Error())) 14 | return "", fmt.Errorf("error during generate project: %s", err.Error()) 15 | } 16 | } 17 | 18 | if _, _, err := utils.GenerateCompressTask(hash, "addition/generation/data/out", path, path); err != nil { 19 | return "", fmt.Errorf("error during generate compress task: %s", err.Error()) 20 | } 21 | 22 | return hash, nil 23 | } 24 | -------------------------------------------------------------------------------- /addition/router.go: -------------------------------------------------------------------------------- 1 | package addition 2 | 3 | import ( 4 | "chat/addition/article" 5 | "chat/addition/card" 6 | "chat/addition/generation" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func Register(app *gin.RouterGroup) { 11 | { 12 | app.POST("/card", card.HandlerAPI) 13 | 14 | app.GET("/generation/create", generation.GenerateAPI) 15 | app.GET("/generation/download/tar", generation.ProjectTarDownloadAPI) 16 | app.GET("/generation/download/zip", generation.ProjectZipDownloadAPI) 17 | 18 | app.GET("/article/create", article.GenerateAPI) 19 | app.GET("/article/download/tar", article.ProjectTarDownloadAPI) 20 | app.GET("/article/download/zip", article.ProjectZipDownloadAPI) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /addition/web/duckduckgo.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "chat/channel" 5 | "chat/utils" 6 | "fmt" 7 | "net/url" 8 | "strings" 9 | ) 10 | 11 | type DDGResponse struct { 12 | Results []struct { 13 | Body string `json:"body"` 14 | Href string `json:"href"` 15 | Title string `json:"title"` 16 | } `json:"results"` 17 | } 18 | 19 | func formatResponse(data *DDGResponse) string { 20 | res := make([]string, 0) 21 | for _, item := range data.Results { 22 | if item.Body == "" || item.Href == "" || item.Title == "" { 23 | continue 24 | } 25 | 26 | res = append(res, fmt.Sprintf("%s (%s): %s", item.Title, item.Href, item.Body)) 27 | } 28 | 29 | return strings.Join(res, "\n") 30 | } 31 | 32 | func CallDuckDuckGoAPI(query string) *DDGResponse { 33 | data, err := utils.Get(fmt.Sprintf( 34 | "%s/search?q=%s&max_results=%d", 35 | channel.SystemInstance.GetSearchEndpoint(), 36 | url.QueryEscape(query), 37 | channel.SystemInstance.GetSearchQuery(), 38 | ), nil) 39 | 40 | if err != nil { 41 | return nil 42 | } 43 | 44 | return utils.MapToStruct[DDGResponse](data) 45 | } 46 | -------------------------------------------------------------------------------- /addition/web/search.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "chat/utils" 5 | "net/url" 6 | ) 7 | 8 | func GetBingUrl(q string) string { 9 | return "https://bing.com/search?q=" + url.QueryEscape(q) 10 | } 11 | 12 | func RequestWithUA(url string) string { 13 | data, err := utils.GetRaw(url, map[string]string{ 14 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0", 15 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 16 | }) 17 | 18 | if err != nil { 19 | return "" 20 | } 21 | 22 | return data 23 | } 24 | 25 | func SearchWebResult(q string) string { 26 | if res := CallDuckDuckGoAPI(q); res != nil { 27 | if resp := formatResponse(res); resp != "" { 28 | return resp 29 | } 30 | } 31 | 32 | uri := GetBingUrl(q) 33 | if res := CallPilotAPI(uri); res != nil { 34 | return utils.Marshal(res.Results) 35 | } 36 | data := RequestWithUA(uri) 37 | return ParseBing(data) 38 | } 39 | -------------------------------------------------------------------------------- /addition/web/utils.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "chat/globals" 5 | "chat/manager/conversation" 6 | ) 7 | 8 | func UsingWebSegment(instance *conversation.Conversation) []globals.Message { 9 | segment := conversation.CopyMessage(instance.GetChatMessage()) 10 | 11 | if instance.IsEnableWeb() { 12 | segment = ChatWithWeb(segment) 13 | } 14 | 15 | return segment 16 | } 17 | 18 | func UsingWebNativeSegment(enable bool, message []globals.Message) []globals.Message { 19 | if enable { 20 | return ChatWithWeb(message) 21 | } else { 22 | return message 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /addition/web/webpilot.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "chat/utils" 5 | "github.com/google/uuid" 6 | ) 7 | 8 | type PilotResponseResult struct { 9 | Title string `json:"title"` 10 | Link string `json:"link"` 11 | Snippet string `json:"snippet"` 12 | } 13 | 14 | type PilotResponse struct { 15 | Results []PilotResponseResult `json:"extra_search_results" required:"true"` 16 | } 17 | 18 | func GenerateFriendUID() string { 19 | return uuid.New().String() 20 | } 21 | 22 | func CallPilotAPI(url string) *PilotResponse { 23 | data, err := utils.Post("https://webreader.webpilotai.com/api/visit-web", map[string]string{ 24 | "Content-Type": "application/json", 25 | "WebPilot-Friend-UID": GenerateFriendUID(), 26 | }, map[string]interface{}{ 27 | "link": url, 28 | "user_has_request": false, 29 | }) 30 | 31 | if err != nil { 32 | return nil 33 | } 34 | 35 | return utils.MapToStruct[PilotResponse](data) 36 | } 37 | -------------------------------------------------------------------------------- /admin/format.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func getMonth() string { 9 | date := time.Now() 10 | return date.Format("2006-01") 11 | } 12 | 13 | func getDay() string { 14 | date := time.Now() 15 | return date.Format("2006-01-02") 16 | } 17 | 18 | func getDays(n int) []time.Time { 19 | current := time.Now() 20 | var days []time.Time 21 | for i := n; i > 0; i-- { 22 | days = append(days, current.AddDate(0, 0, -i+1)) 23 | } 24 | 25 | return days 26 | } 27 | 28 | func getErrorFormat(t string) string { 29 | return fmt.Sprintf("nio:err-analysis-%s", t) 30 | } 31 | 32 | func getBillingFormat(t string) string { 33 | return fmt.Sprintf("nio:billing-analysis-%s", t) 34 | } 35 | 36 | func getMonthBillingFormat(t string) string { 37 | return fmt.Sprintf("nio:billing-analysis-%s", t) 38 | } 39 | 40 | func getRequestFormat(t string) string { 41 | return fmt.Sprintf("nio:request-analysis-%s", t) 42 | } 43 | 44 | func getModelFormat(t string, model string) string { 45 | return fmt.Sprintf("nio:model-analysis-%s-%s", model, t) 46 | } 47 | -------------------------------------------------------------------------------- /admin/instance.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | var MarketInstance *Market 4 | 5 | func InitInstance() { 6 | MarketInstance = NewMarket() 7 | } 8 | -------------------------------------------------------------------------------- /admin/logger.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | "chat/globals" 5 | "chat/utils" 6 | "fmt" 7 | "github.com/gin-gonic/gin" 8 | "strings" 9 | ) 10 | 11 | type LogFile struct { 12 | Path string `json:"path"` 13 | Size int64 `json:"size"` 14 | } 15 | 16 | func ListLogs() []LogFile { 17 | return utils.Each(utils.Walk("logs"), func(path string) LogFile { 18 | return LogFile{ 19 | Path: strings.TrimLeft(path, "logs/"), 20 | Size: utils.GetFileSize(path), 21 | } 22 | }) 23 | } 24 | 25 | func getLogPath(path string) string { 26 | return fmt.Sprintf("logs/%s", path) 27 | } 28 | 29 | func getBlobFile(c *gin.Context, path string) { 30 | c.File(getLogPath(path)) 31 | } 32 | 33 | func deleteLogFile(path string) error { 34 | return utils.DeleteFile(getLogPath(path)) 35 | } 36 | 37 | func getLatestLogs(n int) string { 38 | if n <= 0 { 39 | n = 100 40 | } 41 | 42 | content, err := utils.ReadFileLatestLines(getLogPath(globals.DefaultLoggerFile), n) 43 | 44 | if err != nil { 45 | return fmt.Sprintf("read error: %s", err.Error()) 46 | } 47 | 48 | return content 49 | } 50 | -------------------------------------------------------------------------------- /admin/market.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | "chat/globals" 5 | "fmt" 6 | "github.com/spf13/viper" 7 | ) 8 | 9 | type ModelTag []string 10 | type MarketModel struct { 11 | Id string `json:"id" mapstructure:"id" required:"true"` 12 | Name string `json:"name" mapstructure:"name" required:"true"` 13 | Description string `json:"description" mapstructure:"description"` 14 | Default bool `json:"default" mapstructure:"default"` 15 | HighContext bool `json:"high_context" mapstructure:"high_context"` 16 | Avatar string `json:"avatar" mapstructure:"avatar"` 17 | Tag ModelTag `json:"tag" mapstructure:"tag"` 18 | } 19 | type MarketModelList []MarketModel 20 | 21 | type Market struct { 22 | Models MarketModelList `json:"models" mapstructure:"models"` 23 | } 24 | 25 | func NewMarket() *Market { 26 | var models MarketModelList 27 | if err := viper.UnmarshalKey("market", &models); err != nil { 28 | globals.Warn(fmt.Sprintf("[market] read config error: %s, use default config", err.Error())) 29 | models = MarketModelList{} 30 | } 31 | 32 | return &Market{ 33 | Models: models, 34 | } 35 | } 36 | 37 | func (m *Market) GetModels() MarketModelList { 38 | return m.Models 39 | } 40 | 41 | func (m *Market) GetModel(id string) *MarketModel { 42 | for _, model := range m.Models { 43 | if model.Id == id { 44 | return &model 45 | } 46 | } 47 | return nil 48 | } 49 | 50 | func (m *Market) SaveConfig() error { 51 | viper.Set("market", m.Models) 52 | return viper.WriteConfig() 53 | } 54 | 55 | func (m *Market) SetModels(models MarketModelList) error { 56 | m.Models = models 57 | return m.SaveConfig() 58 | } 59 | -------------------------------------------------------------------------------- /admin/redeem.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | "chat/utils" 5 | "database/sql" 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | func GetRedeemData(db *sql.DB) []RedeemData { 11 | var data []RedeemData 12 | 13 | rows, err := db.Query(` 14 | SELECT quota, COUNT(*) AS total, SUM(IF(used = 0, 0, 1)) AS used 15 | FROM redeem 16 | GROUP BY quota 17 | `) 18 | if err != nil { 19 | return data 20 | } 21 | 22 | for rows.Next() { 23 | var d RedeemData 24 | if err := rows.Scan(&d.Quota, &d.Total, &d.Used); err != nil { 25 | return data 26 | } 27 | data = append(data, d) 28 | } 29 | 30 | return data 31 | } 32 | 33 | func GenerateRedeemCodes(db *sql.DB, num int, quota float32) RedeemGenerateResponse { 34 | arr := make([]string, 0) 35 | idx := 0 36 | for idx < num { 37 | code, err := CreateRedeemCode(db, quota) 38 | 39 | if err != nil { 40 | return RedeemGenerateResponse{ 41 | Status: false, 42 | Message: err.Error(), 43 | } 44 | } 45 | arr = append(arr, code) 46 | idx++ 47 | } 48 | 49 | return RedeemGenerateResponse{ 50 | Status: true, 51 | Data: arr, 52 | } 53 | } 54 | 55 | func CreateRedeemCode(db *sql.DB, quota float32) (string, error) { 56 | code := fmt.Sprintf("nio-%s", utils.GenerateChar(32)) 57 | _, err := db.Exec(` 58 | INSERT INTO redeem (code, quota) VALUES (?, ?) 59 | `, code, quota) 60 | 61 | if err != nil && strings.Contains(err.Error(), "Duplicate entry") { 62 | // code name is duplicate 63 | return CreateRedeemCode(db, quota) 64 | } 65 | 66 | return code, err 67 | } 68 | -------------------------------------------------------------------------------- /admin/router.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | "chat/channel" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func Register(app *gin.RouterGroup) { 9 | channel.Register(app) 10 | 11 | app.GET("/admin/analytics/info", InfoAPI) 12 | app.GET("/admin/analytics/model", ModelAnalysisAPI) 13 | app.GET("/admin/analytics/request", RequestAnalysisAPI) 14 | app.GET("/admin/analytics/billing", BillingAnalysisAPI) 15 | app.GET("/admin/analytics/error", ErrorAnalysisAPI) 16 | app.GET("/admin/analytics/user", UserTypeAnalysisAPI) 17 | 18 | app.GET("/admin/invitation/list", InvitationPaginationAPI) 19 | app.POST("/admin/invitation/generate", GenerateInvitationAPI) 20 | 21 | app.GET("/admin/redeem/list", RedeemListAPI) 22 | app.POST("/admin/redeem/generate", GenerateRedeemAPI) 23 | 24 | app.GET("/admin/user/list", UserPaginationAPI) 25 | app.POST("/admin/user/quota", UserQuotaAPI) 26 | app.POST("/admin/user/subscription", UserSubscriptionAPI) 27 | app.POST("/admin/user/root", UpdateRootPasswordAPI) 28 | 29 | app.POST("/admin/market/update", UpdateMarketAPI) 30 | 31 | app.GET("/admin/logger/list", ListLoggerAPI) 32 | app.GET("/admin/logger/download", DownloadLoggerAPI) 33 | app.GET("/admin/logger/console", ConsoleLoggerAPI) 34 | app.POST("/admin/logger/delete", DeleteLoggerAPI) 35 | } 36 | -------------------------------------------------------------------------------- /admin/statistic.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | "chat/connection" 5 | "chat/utils" 6 | "github.com/go-redis/redis/v8" 7 | "time" 8 | ) 9 | 10 | func IncrErrorRequest(cache *redis.Client) { 11 | utils.IncrOnce(cache, getErrorFormat(getDay()), time.Hour*24*7*2) 12 | } 13 | 14 | func IncrBillingRequest(cache *redis.Client, amount int64) { 15 | utils.IncrWithExpire(cache, getBillingFormat(getDay()), amount, time.Hour*24*30*2) 16 | utils.IncrWithExpire(cache, getMonthBillingFormat(getMonth()), amount, time.Hour*24*30*2) 17 | } 18 | 19 | func IncrRequest(cache *redis.Client) { 20 | utils.IncrOnce(cache, getRequestFormat(getDay()), time.Hour*24*7*2) 21 | } 22 | 23 | func IncrModelRequest(cache *redis.Client, model string, tokens int64) { 24 | utils.IncrWithExpire(cache, getModelFormat(getDay(), model), tokens, time.Hour*24*7*2) 25 | } 26 | 27 | func AnalysisRequest(model string, buffer *utils.Buffer, err error) { 28 | instance := connection.Cache 29 | 30 | if err != nil && err.Error() != "signal" { 31 | IncrErrorRequest(instance) 32 | return 33 | } 34 | 35 | IncrRequest(instance) 36 | IncrModelRequest(instance, model, int64(buffer.CountToken())) 37 | } 38 | -------------------------------------------------------------------------------- /app/.env.deeptrain: -------------------------------------------------------------------------------- 1 | VITE_USE_DEEPTRAIN=true 2 | VITE_BACKEND_ENDPOINT=https://api.chatnio.net 3 | -------------------------------------------------------------------------------- /app/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | dev-dist 14 | *.local 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | 27 | # Libre 28 | db 29 | -------------------------------------------------------------------------------- /app/.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /app/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "src/components", 14 | "utils": "@/components/ui/lib/utils" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Chat Nio 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/favicon.ico -------------------------------------------------------------------------------- /app/public/icons/360gpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/360gpt.png -------------------------------------------------------------------------------- /app/public/icons/baichuan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/baichuan.png -------------------------------------------------------------------------------- /app/public/icons/chatglm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/chatglm.png -------------------------------------------------------------------------------- /app/public/icons/claude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/claude.png -------------------------------------------------------------------------------- /app/public/icons/claude100k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/claude100k.png -------------------------------------------------------------------------------- /app/public/icons/dalle.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/dalle.jpeg -------------------------------------------------------------------------------- /app/public/icons/gemini.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/gemini.jpeg -------------------------------------------------------------------------------- /app/public/icons/gpt35turbo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/gpt35turbo.png -------------------------------------------------------------------------------- /app/public/icons/gpt35turbo16k.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/gpt35turbo16k.webp -------------------------------------------------------------------------------- /app/public/icons/gpt4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/gpt4.png -------------------------------------------------------------------------------- /app/public/icons/gpt432k.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/gpt432k.webp -------------------------------------------------------------------------------- /app/public/icons/gpt4dalle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/gpt4dalle.png -------------------------------------------------------------------------------- /app/public/icons/gpt4v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/gpt4v.png -------------------------------------------------------------------------------- /app/public/icons/hunyuan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/hunyuan.png -------------------------------------------------------------------------------- /app/public/icons/llama2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/llama2.webp -------------------------------------------------------------------------------- /app/public/icons/llamacode.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/llamacode.webp -------------------------------------------------------------------------------- /app/public/icons/midjourney.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/midjourney.jpg -------------------------------------------------------------------------------- /app/public/icons/newbing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/newbing.jpg -------------------------------------------------------------------------------- /app/public/icons/palm2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/palm2.webp -------------------------------------------------------------------------------- /app/public/icons/skylark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/skylark.jpg -------------------------------------------------------------------------------- /app/public/icons/sparkdesk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/sparkdesk.jpg -------------------------------------------------------------------------------- /app/public/icons/stablediffusion.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/stablediffusion.jpeg -------------------------------------------------------------------------------- /app/public/icons/tongyi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/icons/tongyi.png -------------------------------------------------------------------------------- /app/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/logo.png -------------------------------------------------------------------------------- /app/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Allow: / 3 | Disallow: /admin/ 4 | -------------------------------------------------------------------------------- /app/public/service.js: -------------------------------------------------------------------------------- 1 | 2 | const SERVICE_NAME = "chatnio"; 3 | 4 | self.addEventListener('activate', function (event) { 5 | console.debug("[service] service worker activated"); 6 | }); 7 | 8 | self.addEventListener('install', function (event) { 9 | event.waitUntil( 10 | caches.open(SERVICE_NAME) 11 | .then(function (cache) { 12 | return cache.addAll([]); 13 | }) 14 | ); 15 | }); 16 | 17 | self.addEventListener('fetch', function (event) { 18 | event.respondWith( 19 | caches.match(event.request) 20 | .then(function (response) { 21 | return response || fetch(event.request); 22 | }) 23 | ); 24 | }); 25 | -------------------------------------------------------------------------------- /app/public/service/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/service/android-chrome-192x192.png -------------------------------------------------------------------------------- /app/public/service/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/service/android-chrome-512x512.png -------------------------------------------------------------------------------- /app/public/service/favicon-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/service/favicon-64x64.png -------------------------------------------------------------------------------- /app/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chat Nio", 3 | "short_name": "Chat Nio", 4 | "icons": [ 5 | { 6 | "src": "/service/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/service/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": "/", 17 | "theme_color": "#000000", 18 | "background_color": "#0000000", 19 | "display": "standalone" 20 | } 21 | -------------------------------------------------------------------------------- /app/public/source/qq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/public/source/qq.jpg -------------------------------------------------------------------------------- /app/public/workbox.js: -------------------------------------------------------------------------------- 1 | 2 | if ('serviceWorker' in navigator) { 3 | window.addEventListener('load', function () { 4 | navigator.serviceWorker.register('/service.js').then(function (registration) { 5 | console.debug(`[service] service worker registered with scope: ${registration.scope}`); 6 | }, function (err) { 7 | console.debug(`[service] service worker registration failed: ${err}`); 8 | }); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /app/qodana.yaml: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------------# 2 | # Qodana analysis is configured by qodana.yaml file # 3 | # https://www.jetbrains.com/help/qodana/qodana-yaml.html # 4 | #-------------------------------------------------------------------------------# 5 | version: "1.0" 6 | 7 | #Specify inspection profile for code analysis 8 | profile: 9 | name: qodana.starter 10 | 11 | #Enable inspections 12 | #include: 13 | # - name: 14 | 15 | #Disable inspections 16 | #exclude: 17 | # - name: 18 | # paths: 19 | # - 20 | 21 | #Execute shell command before Qodana execution (Applied in CI/CD pipeline) 22 | #bootstrap: sh ./prepare-qodana.sh 23 | 24 | #Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) 25 | #plugins: 26 | # - id: #(plugin id can be found at https://plugins.jetbrains.com) 27 | 28 | #Specify Qodana linter for analysis (Applied in CI/CD pipeline) 29 | linter: jetbrains/qodana-js:latest 30 | -------------------------------------------------------------------------------- /app/src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | -------------------------------------------------------------------------------- /app/src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "app" 3 | version = "0.1.0" 4 | description = "Your Next Powerful AI Chat Platform" 5 | authors = ["Deeptrain Team"] 6 | license = "Apache-2.0" 7 | repository = "https://github.com/Deeptrain-Community/chatnio" 8 | default-run = "app" 9 | edition = "2021" 10 | rust-version = "1.60" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [build-dependencies] 15 | tauri-build = { version = "1.5.0", features = [] } 16 | 17 | [dependencies] 18 | serde_json = "1.0" 19 | serde = { version = "1.0", features = ["derive"] } 20 | tauri = { version = "1.5.2", features = [ "updater", "system-tray"] } 21 | 22 | [features] 23 | # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. 24 | # If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes. 25 | # DO NOT REMOVE!! 26 | custom-protocol = [ "tauri/custom-protocol" ] 27 | -------------------------------------------------------------------------------- /app/src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /app/src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /app/src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /app/src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /app/src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /app/src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /app/src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /app/src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /app/src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /app/src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /app/src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /app/src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /app/src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /app/src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /app/src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /app/src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /app/src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapyan/woochatnio/24102a01d2ea48a7bda2870cea9c43ae4056e5dc/app/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /app/src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | fn main() { 5 | tauri::Builder::default() 6 | .run(tauri::generate_context!()) 7 | .expect("error while running tauri application"); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Provider } from "react-redux"; 2 | import store from "./store/index.ts"; 3 | import AppProvider from "./components/app/AppProvider.tsx"; 4 | import { AppRouter } from "./router.tsx"; 5 | import { Toaster } from "@/components/ui/sonner"; 6 | import Spinner from "@/spinner.tsx"; 7 | 8 | function App() { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /app/src/admin/api/charge.ts: -------------------------------------------------------------------------------- 1 | import { CommonResponse } from "@/admin/utils.ts"; 2 | import { ChargeProps } from "@/admin/charge.ts"; 3 | import { getErrorMessage } from "@/utils/base.ts"; 4 | import axios from "axios"; 5 | 6 | export type ChargeListResponse = CommonResponse & { 7 | data: ChargeProps[]; 8 | }; 9 | 10 | export type ChargeSyncRequest = { 11 | overwrite: boolean; 12 | data: ChargeProps[]; 13 | }; 14 | 15 | export async function listCharge(): Promise { 16 | try { 17 | const response = await axios.get("/admin/charge/list"); 18 | return response.data as ChargeListResponse; 19 | } catch (e) { 20 | return { status: false, error: getErrorMessage(e), data: [] }; 21 | } 22 | } 23 | 24 | export async function setCharge(charge: ChargeProps): Promise { 25 | try { 26 | const response = await axios.post(`/admin/charge/set`, charge); 27 | return response.data as CommonResponse; 28 | } catch (e) { 29 | return { status: false, error: getErrorMessage(e) }; 30 | } 31 | } 32 | 33 | export async function deleteCharge(id: number): Promise { 34 | try { 35 | const response = await axios.get(`/admin/charge/delete/${id}`); 36 | return response.data as CommonResponse; 37 | } catch (e) { 38 | return { status: false, error: getErrorMessage(e) }; 39 | } 40 | } 41 | 42 | export async function syncCharge( 43 | data: ChargeSyncRequest, 44 | ): Promise { 45 | try { 46 | const response = await axios.post(`/admin/charge/sync`, data); 47 | return response.data as CommonResponse; 48 | } catch (e) { 49 | return { status: false, error: getErrorMessage(e) }; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/admin/api/info.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { 3 | setAnnouncement, 4 | setAppLogo, 5 | setAppName, 6 | setBlobEndpoint, 7 | setBuyLink, 8 | setDocsUrl, 9 | } from "@/conf/env.ts"; 10 | 11 | export type SiteInfo = { 12 | title: string; 13 | logo: string; 14 | docs: string; 15 | file: string; 16 | announcement: string; 17 | buy_link: string; 18 | }; 19 | 20 | export async function getSiteInfo(): Promise { 21 | try { 22 | const response = await axios.get("/info"); 23 | return response.data as SiteInfo; 24 | } catch (e) { 25 | console.warn(e); 26 | return { 27 | title: "", 28 | logo: "", 29 | docs: "", 30 | file: "", 31 | announcement: "", 32 | buy_link: "", 33 | }; 34 | } 35 | } 36 | 37 | export function syncSiteInfo() { 38 | getSiteInfo().then((info) => { 39 | setAppName(info.title); 40 | setAppLogo(info.logo); 41 | setDocsUrl(info.docs); 42 | setBlobEndpoint(info.file); 43 | setAnnouncement(info.announcement); 44 | setBuyLink(info.buy_link); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /app/src/admin/api/logger.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { CommonResponse } from "@/admin/utils.ts"; 3 | import { getErrorMessage } from "@/utils/base.ts"; 4 | 5 | export type Logger = { 6 | path: string; 7 | size: number; 8 | }; 9 | 10 | export async function listLoggers(): Promise { 11 | try { 12 | const response = await axios.get("/admin/logger/list"); 13 | return (response.data || []) as Logger[]; 14 | } catch (e) { 15 | console.warn(e); 16 | return []; 17 | } 18 | } 19 | 20 | export async function getLoggerConsole(n?: number): Promise { 21 | try { 22 | const response = await axios.get(`/admin/logger/console?n=${n ?? 100}`); 23 | return response.data.content as string; 24 | } catch (e) { 25 | console.warn(e); 26 | return `failed to get info from server: ${getErrorMessage(e)}`; 27 | } 28 | } 29 | 30 | export async function downloadLogger(path: string): Promise { 31 | try { 32 | const response = await axios.get("/admin/logger/download", { 33 | responseType: "blob", 34 | params: { path }, 35 | }); 36 | const url = window.URL.createObjectURL(new Blob([response.data])); 37 | const link = document.createElement("a"); 38 | link.href = url; 39 | link.setAttribute("download", path); 40 | document.body.appendChild(link); 41 | link.click(); 42 | } catch (e) { 43 | console.warn(e); 44 | } 45 | } 46 | 47 | export async function deleteLogger(path: string): Promise { 48 | try { 49 | const response = await axios.post(`/admin/logger/delete?path=${path}`); 50 | return response.data as CommonResponse; 51 | } catch (e) { 52 | console.warn(e); 53 | return { status: false, error: getErrorMessage(e) }; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/admin/api/market.ts: -------------------------------------------------------------------------------- 1 | import { Model } from "@/api/types.ts"; 2 | import { CommonResponse } from "@/admin/utils.ts"; 3 | import axios from "axios"; 4 | import { getErrorMessage } from "@/utils/base.ts"; 5 | 6 | export async function updateMarket(data: Model[]): Promise { 7 | try { 8 | const resp = await axios.post("/admin/market/update", data); 9 | return resp.data as CommonResponse; 10 | } catch (e) { 11 | console.warn(e); 12 | return { status: false, error: getErrorMessage(e) }; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/admin/api/plan.ts: -------------------------------------------------------------------------------- 1 | import { Plan } from "@/api/types"; 2 | import axios from "axios"; 3 | import { CommonResponse } from "@/admin/utils.ts"; 4 | import { getErrorMessage } from "@/utils/base.ts"; 5 | 6 | export type PlanConfig = { 7 | enabled: boolean; 8 | plans: Plan[]; 9 | }; 10 | 11 | export async function getPlanConfig(): Promise { 12 | try { 13 | const response = await axios.get("/admin/plan/view"); 14 | const conf = response.data as PlanConfig; 15 | conf.plans = (conf.plans || []).filter((item) => item.level > 0); 16 | if (conf.plans.length === 0) 17 | conf.plans = [1, 2, 3].map( 18 | (level) => ({ level, price: 0, items: [] }) as Plan, 19 | ); 20 | return conf; 21 | } catch (e) { 22 | console.warn(e); 23 | return { enabled: false, plans: [] }; 24 | } 25 | } 26 | 27 | export async function setPlanConfig( 28 | config: PlanConfig, 29 | ): Promise { 30 | try { 31 | const response = await axios.post(`/admin/plan/update`, config); 32 | return response.data as CommonResponse; 33 | } catch (e) { 34 | return { status: false, error: getErrorMessage(e) }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/admin/charge.ts: -------------------------------------------------------------------------------- 1 | export const tokenBilling = "token-billing"; 2 | export const timesBilling = "times-billing"; 3 | export const nonBilling = "non-billing"; 4 | 5 | export const defaultChargeType = tokenBilling; 6 | export const chargeTypes = [nonBilling, timesBilling, tokenBilling]; 7 | 8 | export type ChargeProps = { 9 | id: number; 10 | models: string[]; 11 | type: string; 12 | anonymous: boolean; 13 | input: number; 14 | output: number; 15 | }; 16 | -------------------------------------------------------------------------------- /app/src/admin/market.ts: -------------------------------------------------------------------------------- 1 | export const marketEditableTags = [ 2 | "official", 3 | "unstable", 4 | "web", 5 | "high-quality", 6 | "high-price", 7 | "open-source", 8 | "image-generation", 9 | "multi-modal", 10 | "fast", 11 | "english-model", 12 | ]; 13 | 14 | export const modelImages = [ 15 | "gpt35turbo.png", 16 | "gpt35turbo16k.webp", 17 | "gpt4.png", 18 | "gpt432k.webp", 19 | "gpt4v.png", 20 | "gpt4dalle.png", 21 | "claude.png", 22 | "claude100k.png", 23 | "stablediffusion.jpeg", 24 | "llama2.webp", 25 | "llamacode.webp", 26 | "dalle.jpeg", 27 | "midjourney.jpg", 28 | "newbing.jpg", 29 | "palm2.webp", 30 | "gemini.jpeg", 31 | "chatglm.png", 32 | "tongyi.png", 33 | "sparkdesk.jpg", 34 | "hunyuan.png", 35 | "360gpt.png", 36 | "baichuan.png", 37 | "skylark.jpg", 38 | ]; 39 | 40 | export const marketTags = [...marketEditableTags, "free", "high-context"]; 41 | -------------------------------------------------------------------------------- /app/src/admin/utils.ts: -------------------------------------------------------------------------------- 1 | export type CommonResponse = { 2 | status: boolean; 3 | error: string; 4 | reason?: string; 5 | }; 6 | 7 | export function toastState( 8 | toast: any, 9 | t: any, 10 | state: CommonResponse, 11 | toastSuccess?: boolean, 12 | ) { 13 | if (state.status) 14 | toastSuccess && 15 | toast({ title: t("success"), description: t("request-success") }); 16 | else toast({ title: t("error"), description: state.error ?? state.reason }); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/api/file.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { blobEndpoint } from "@/conf/env.ts"; 3 | 4 | export type BlobParserResponse = { 5 | status: boolean; 6 | content: string; 7 | error?: string; 8 | }; 9 | 10 | export type FileObject = { 11 | name: string; 12 | content: string; 13 | size?: number; 14 | }; 15 | 16 | export type FileArray = FileObject[]; 17 | 18 | export async function blobParser(file: File): Promise { 19 | try { 20 | const resp = await axios.post( 21 | `${blobEndpoint}/upload`, 22 | { file }, 23 | { 24 | headers: { "Content-Type": "multipart/form-data" }, 25 | }, 26 | ); 27 | 28 | return resp.data as BlobParserResponse; 29 | } catch (e) { 30 | return { status: false, content: "", error: (e as Error).message }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/api/invitation.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export type InvitationResponse = { 4 | status: boolean; 5 | error: string; 6 | quota: number; 7 | }; 8 | 9 | export async function getInvitation(code: string): Promise { 10 | try { 11 | const resp = await axios.get(`/invite?code=${code}`); 12 | return resp.data as InvitationResponse; 13 | } catch (e) { 14 | console.debug(e); 15 | return { status: false, error: "network error", quota: 0 }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/api/quota.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export async function getQuota(): Promise { 4 | try { 5 | const response = await axios.get("/quota"); 6 | if (response.data.status) { 7 | return response.data.quota as number; 8 | } 9 | } catch (e) { 10 | console.debug(e); 11 | } 12 | 13 | return NaN; 14 | } 15 | -------------------------------------------------------------------------------- /app/src/api/redeem.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { getErrorMessage } from "@/utils/base.ts"; 3 | 4 | export type RedeemResponse = { 5 | status: boolean; 6 | error: string; 7 | quota: number; 8 | }; 9 | 10 | export async function useRedeem(code: string): Promise { 11 | try { 12 | const resp = await axios.get(`/redeem?code=${code}`); 13 | return resp.data as RedeemResponse; 14 | } catch (e) { 15 | console.debug(e); 16 | return { 17 | status: false, 18 | error: `network error: ${getErrorMessage(e)}`, 19 | quota: 0, 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/api/types.ts: -------------------------------------------------------------------------------- 1 | import { Conversation } from "./conversation.ts"; 2 | 3 | export type Message = { 4 | role: string; 5 | content: string; 6 | keyword?: string; 7 | quota?: number; 8 | end?: boolean; 9 | plan?: boolean; 10 | }; 11 | 12 | export type Model = { 13 | id: string; 14 | name: string; 15 | description?: string; 16 | free: boolean; 17 | auth: boolean; 18 | default: boolean; 19 | high_context: boolean; 20 | avatar: string; 21 | tag?: string[]; 22 | }; 23 | 24 | export type Id = number; 25 | 26 | export type ConversationInstance = { 27 | id: number; 28 | name: string; 29 | message: Message[]; 30 | model?: string; 31 | shared?: boolean; 32 | }; 33 | 34 | export type ConversationMapper = Record; 35 | 36 | export type PlanItem = { 37 | id: string; 38 | name: string; 39 | value: number; 40 | icon: string; 41 | models: string[]; 42 | }; 43 | 44 | export type Plan = { 45 | level: number; 46 | price: number; 47 | items: PlanItem[]; 48 | }; 49 | 50 | export type Plans = Plan[]; 51 | -------------------------------------------------------------------------------- /app/src/api/v1.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { Model, Plan } from "@/api/types.ts"; 3 | import { ChargeProps } from "@/admin/charge.ts"; 4 | 5 | type v1Options = { 6 | endpoint?: string; 7 | }; 8 | 9 | export function getV1Path(path: string, options?: v1Options): string { 10 | let endpoint = options && options.endpoint ? options.endpoint : ""; 11 | if (endpoint.endsWith("/")) endpoint = endpoint.slice(0, -1); 12 | 13 | return endpoint + path; 14 | } 15 | 16 | export async function getApiModels(options?: v1Options): Promise { 17 | try { 18 | const res = await axios.get(getV1Path("/v1/models", options)); 19 | return res.data as string[]; 20 | } catch (e) { 21 | console.warn(e); 22 | return []; 23 | } 24 | } 25 | 26 | export async function getApiPlans(options?: v1Options): Promise { 27 | try { 28 | const res = await axios.get(getV1Path("/v1/plans", options)); 29 | const plans = res.data as Plan[]; 30 | return plans.filter((plan: Plan) => plan.level !== 0); 31 | } catch (e) { 32 | console.warn(e); 33 | return []; 34 | } 35 | } 36 | 37 | export async function getApiMarket(options?: v1Options): Promise { 38 | try { 39 | const res = await axios.get(getV1Path("/v1/market", options)); 40 | return (res.data || []) as Model[]; 41 | } catch (e) { 42 | console.warn(e); 43 | return []; 44 | } 45 | } 46 | 47 | export async function getApiCharge( 48 | options?: v1Options, 49 | ): Promise { 50 | try { 51 | const res = await axios.get(getV1Path("/v1/charge", options)); 52 | return res.data as ChargeProps[]; 53 | } catch (e) { 54 | console.warn(e); 55 | return []; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/assets/admin/all.less: -------------------------------------------------------------------------------- 1 | @import "menu"; 2 | @import "dashboard"; 3 | @import "market"; 4 | @import "management"; 5 | @import "broadcast"; 6 | @import "channel"; 7 | @import "charge"; 8 | @import "system"; 9 | @import "subscription"; 10 | @import "logger"; 11 | 12 | 13 | .admin-page { 14 | position: relative; 15 | display: flex; 16 | flex-direction: row; 17 | width: 100%; 18 | min-height: calc(100vh - 56px); 19 | height: max-content; 20 | } 21 | 22 | .admin-card { 23 | border: 0 !important; 24 | } 25 | 26 | 27 | @media (max-width: 768px) { 28 | .admin-card { 29 | border-radius: 0 !important; 30 | } 31 | 32 | .user-interface, 33 | .market, 34 | .broadcast, 35 | .channel, 36 | .charge, 37 | .system, 38 | .logger, 39 | .admin-subscription 40 | { 41 | padding: 0 !important; 42 | 43 | & > * { 44 | margin-bottom: 0 !important; 45 | border-bottom: 1px solid hsl(var(--border)) !important; 46 | border-radius: 0 !important; 47 | } 48 | } 49 | } 50 | 51 | .object-id { 52 | display: flex; 53 | flex-direction: row; 54 | align-items: center; 55 | justify-items: center; 56 | border-radius: var(--radius); 57 | border: 1px solid hsl(var(--border)); 58 | color: hsl(var(--text-secondary)); 59 | user-select: none; 60 | font-size: 0.75rem; 61 | height: 2.5rem; 62 | padding: 0.5rem 1.25rem; 63 | cursor: pointer; 64 | transition: 0.25s; 65 | flex-shrink: 0; 66 | 67 | &:hover { 68 | color: hsl(var(--text)); 69 | border-color: hsl(var(--border-hover)); 70 | } 71 | 72 | svg { 73 | transform: translateY(1px); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/assets/admin/broadcast.less: -------------------------------------------------------------------------------- 1 | .broadcast { 2 | width: 100%; 3 | height: max-content; 4 | padding: 2rem; 5 | display: flex; 6 | flex-direction: column; 7 | 8 | .broadcast-card { 9 | width: 100%; 10 | height: 100%; 11 | min-height: 20vh; 12 | } 13 | 14 | .empty { 15 | color: hsl(var(--text-secondary)) !important; 16 | font-size: 14px; 17 | margin: auto; 18 | user-select: none; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/assets/admin/charge.less: -------------------------------------------------------------------------------- 1 | .charge { 2 | width: 100%; 3 | height: max-content; 4 | padding: 2rem; 5 | display: flex; 6 | flex-direction: column; 7 | 8 | .charge-card { 9 | width: 100%; 10 | height: 100%; 11 | min-height: 20vh; 12 | } 13 | } 14 | 15 | .charge-widget { 16 | height: max-content; 17 | width: 100%; 18 | display: flex; 19 | flex-direction: column; 20 | 21 | & > * { 22 | margin-bottom: 1rem; 23 | 24 | &:last-child { 25 | margin-bottom: 0; 26 | } 27 | } 28 | } 29 | 30 | .charge-alert { 31 | .model-list { 32 | display: flex; 33 | flex-direction: row; 34 | flex-wrap: wrap; 35 | gap: 0.5rem; 36 | margin-top: 1rem; 37 | 38 | .model { 39 | padding: 0.5rem 0.75rem; 40 | border-radius: var(--radius); 41 | border: 1px solid hsl(var(--border)); 42 | } 43 | } 44 | } 45 | 46 | .charge-editor { 47 | padding: 1.5rem; 48 | border: 1px solid hsl(var(--border)); 49 | border-radius: var(--radius); 50 | 51 | .token { 52 | color: hsl(var(--text-secondary)); 53 | user-select: none; 54 | } 55 | } 56 | 57 | 58 | .charge-table { 59 | border: 1px solid hsl(var(--border)); 60 | border-radius: var(--radius); 61 | overflow-x: auto; 62 | scrollbar-width: thin; 63 | 64 | &::-webkit-scrollbar { 65 | width: 0.5rem; 66 | } 67 | 68 | .table { 69 | scrollbar-width: thin; 70 | } 71 | 72 | .charge-id { 73 | color: hsl(var(--text-secondary)); 74 | user-select: none; 75 | 76 | &:before { 77 | content: '#'; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/assets/admin/management.less: -------------------------------------------------------------------------------- 1 | .user-interface { 2 | display: flex; 3 | flex-direction: column; 4 | width: 100%; 5 | height: calc(100vh - 56px); 6 | padding: 2rem; 7 | 8 | & > * { 9 | margin-bottom: 2rem; 10 | 11 | &:last-child { 12 | margin-bottom: 0; 13 | } 14 | } 15 | 16 | &.mobile { 17 | padding: 1rem; 18 | } 19 | 20 | .pagination { 21 | display: flex; 22 | justify-content: center; 23 | align-items: center; 24 | margin-top: 1rem; 25 | 26 | & > * { 27 | scale: 0.8; 28 | margin: 0 0.5rem; 29 | } 30 | } 31 | 32 | .empty { 33 | user-select: none; 34 | text-align: center; 35 | font-size: 14px; 36 | margin: 4rem 0 2rem; 37 | color: hsl(var(--text-secondary)); 38 | } 39 | 40 | .action { 41 | display: flex; 42 | flex-direction: row; 43 | align-items: center; 44 | margin-top: 1rem; 45 | } 46 | } 47 | 48 | .user-row, 49 | .redeem-row, 50 | .invitation-row { 51 | display: flex; 52 | flex-direction: row; 53 | align-items: center; 54 | justify-content: center; 55 | white-space: nowrap; 56 | user-select: none; 57 | color: hsl(var(--text)); 58 | margin: 1rem 0; 59 | } 60 | 61 | .user-action, 62 | .redeem-action, 63 | .invitation-action { 64 | display: flex; 65 | margin-top: 1rem; 66 | flex-direction: row; 67 | align-items: center; 68 | 69 | & > * { 70 | margin-right: 0.5rem; 71 | 72 | &:last-child { 73 | margin-right: 0; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/assets/admin/system.less: -------------------------------------------------------------------------------- 1 | .system { 2 | width: 100%; 3 | height: max-content; 4 | padding: 2rem; 5 | display: flex; 6 | flex-direction: column; 7 | 8 | .system-card { 9 | width: 100%; 10 | height: 100%; 11 | min-height: 20vh; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/assets/common/404.less: -------------------------------------------------------------------------------- 1 | .error-page { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | height: calc(100vh - 56px); 7 | font-size: 30px; 8 | color: hsl(var(--tw-content)); 9 | gap: 12px; 10 | user-select: none; 11 | 12 | .icon { 13 | width: 58px; 14 | height: 58px; 15 | transform: translateY(-62px); 16 | } 17 | 18 | h1 { 19 | font-size: 48px; 20 | transform: translateY(-50px); 21 | } 22 | 23 | p { 24 | font-size: 24px; 25 | transform: translateY(-32px); 26 | } 27 | 28 | button { 29 | transform: translateY(-4px); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/assets/pages/api.less: -------------------------------------------------------------------------------- 1 | .api-dialog { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | width: 100%; 6 | height: max-content; 7 | padding: 24px 0 12px; 8 | gap: 24px; 9 | } 10 | 11 | .api-wrapper { 12 | display: flex; 13 | flex-direction: row; 14 | gap: 6px; 15 | width: 100%; 16 | 17 | input { 18 | text-align: center; 19 | font-size: 16px; 20 | cursor: pointer; 21 | flex-grow: 1; 22 | } 23 | 24 | button { 25 | flex-shrink: 0; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/assets/pages/article.less: -------------------------------------------------------------------------------- 1 | .article-page { 2 | position: relative; 3 | display: flex; 4 | width: 100%; 5 | min-height: calc(100vh - 56px); 6 | height: max-content; 7 | } 8 | 9 | 10 | .article-container { 11 | display: flex; 12 | flex-direction: column; 13 | padding: 12px 16px; 14 | gap: 6px; 15 | width: 100%; 16 | height: 100%; 17 | } 18 | 19 | .article-wrapper { 20 | width: calc(96vw - 32px); 21 | height: 100%; 22 | margin: 1rem auto; 23 | padding: 1rem; 24 | max-width: 840px; 25 | 26 | .article-title { 27 | display: flex; 28 | flex-direction: row; 29 | user-select: none; 30 | align-items: center; 31 | } 32 | 33 | .article-content { 34 | display: flex; 35 | flex-direction: column; 36 | margin: 1rem 0; 37 | 38 | & > * { 39 | margin-bottom: 1rem; 40 | 41 | &:last-child { 42 | margin-bottom: 0; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/assets/pages/auth.less: -------------------------------------------------------------------------------- 1 | .auth { 2 | width: 100%; 3 | height: calc(100vh - 56px); 4 | overflow: hidden; 5 | background: hsla(var(--background-container)); 6 | } 7 | 8 | .auth-container { 9 | display: flex; 10 | flex-direction: column; 11 | align-items: center; 12 | margin: 2.5rem 2rem; 13 | user-select: none; 14 | 15 | .logo { 16 | width: 4rem; 17 | height: 4rem; 18 | border-radius: var(--radius); 19 | } 20 | 21 | & > * { 22 | margin-bottom: 0.5rem; 23 | 24 | &:last-child { 25 | margin-bottom: 0; 26 | } 27 | } 28 | } 29 | 30 | .auth-card { 31 | width: 80vw; 32 | max-width: 360px; 33 | min-width: 280px; 34 | margin: 1rem 0; 35 | } 36 | 37 | .auth-wrapper { 38 | display: flex; 39 | flex-direction: column; 40 | padding: 1.5rem 0; 41 | 42 | & > * { 43 | margin-bottom: 1rem; 44 | 45 | &:last-child { 46 | margin-bottom: 0; 47 | } 48 | } 49 | } 50 | 51 | .addition-wrapper { 52 | display: flex; 53 | flex-direction: column; 54 | border-radius: var(--radius); 55 | border: 1px solid hsla(var(--border)); 56 | padding: 1.25rem; 57 | align-items: center; 58 | transform: translateY(-1rem); 59 | font-size: 0.875rem; 60 | text-align: center; 61 | 62 | a { 63 | text-decoration: underline; 64 | text-underline-offset: 0.25rem; 65 | text-underline: 2px solid hsl(var(--text-secondary)); 66 | color: hsl(var(--text-secondary)); 67 | transition: 0.2s ease-in-out; 68 | cursor: pointer; 69 | 70 | &:hover { 71 | color: hsl(var(--text)); 72 | text-underline-color: hsl(var(--text)); 73 | } 74 | } 75 | 76 | .row { 77 | margin-bottom: 0.5rem; 78 | 79 | &:last-child { 80 | margin-bottom: 0; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/assets/pages/mask.less: -------------------------------------------------------------------------------- 1 | .mask-dialog { 2 | height: max-content; 3 | max-height: 50vh; 4 | overflow-x: hidden; 5 | overflow-y: auto; 6 | scrollbar-width: thin; 7 | padding: 1rem 0.5rem 0.5rem; 8 | } 9 | 10 | .mask-wrapper { 11 | display: flex; 12 | flex-direction: column; 13 | } 14 | 15 | .mask-list { 16 | display: flex; 17 | flex-direction: column; 18 | user-select: none; 19 | border: 1px solid hsl(var(--border)); 20 | border-radius: var(--radius); 21 | overflow: hidden; 22 | margin-top: 1rem; 23 | 24 | &::-webkit-scrollbar { 25 | width: 0.25rem; 26 | } 27 | } 28 | 29 | .mask-item { 30 | display: flex; 31 | flex-direction: row; 32 | align-items: center; 33 | flex-shrink: 0; 34 | cursor: pointer; 35 | border-bottom: 1px solid hsl(var(--border)); 36 | padding: 1rem 0; 37 | transition: 0.2s ease-in-out; 38 | 39 | .mask-avatar { 40 | width: 2.25rem; 41 | height: 2.25rem; 42 | padding: 0.5rem; 43 | margin-right: 0.75rem; 44 | margin-left: 1rem; 45 | border-radius: var(--radius); 46 | border: 1px solid hsl(var(--border)); 47 | transition: 0.2s ease-in-out; 48 | } 49 | 50 | .mask-content { 51 | display: flex; 52 | flex-direction: column; 53 | 54 | .mask-name { 55 | color: hsl(var(--text)); 56 | margin-right: 0.5rem; 57 | } 58 | 59 | .mask-info { 60 | font-size: 12px; 61 | color: hsl(var(--text-secondary)); 62 | } 63 | } 64 | 65 | &:hover { 66 | background-color: hsl(var(--background-hover)); 67 | 68 | .mask-avatar { 69 | border-color: hsl(var(--border-active)); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/assets/pages/package.less: -------------------------------------------------------------------------------- 1 | .package-wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | margin: 24px 4px !important; 5 | gap: 18px; 6 | 7 | .package { 8 | display: flex; 9 | flex-direction: column; 10 | gap: 8px; 11 | 12 | .package-title { 13 | display: flex; 14 | flex-direction: row; 15 | align-items: center; 16 | gap: 4px; 17 | font-size: 16px; 18 | color: hsl(var(--text)); 19 | user-select: none; 20 | 21 | svg { 22 | transform: translateY(1px); 23 | } 24 | } 25 | 26 | .package-content { 27 | font-size: 14px; 28 | color: hsl(var(--text-secondary)); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/assets/pages/share-manager.less: -------------------------------------------------------------------------------- 1 | .share-table { 2 | max-height: 60vh; 3 | max-width: 85vw; 4 | overflow: auto; 5 | scrollbar-width: thin; 6 | padding: 0 0.5rem; 7 | 8 | &::-webkit-scrollbar { 9 | width: 0.5rem; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/components/Avatar.tsx: -------------------------------------------------------------------------------- 1 | import { deeptrainApiEndpoint, useDeeptrain } from "@/conf/env.ts"; 2 | import { ImgHTMLAttributes, useMemo } from "react"; 3 | import { cn } from "@/components/ui/lib/utils.ts"; 4 | 5 | export interface AvatarProps extends ImgHTMLAttributes { 6 | username: string; 7 | } 8 | 9 | function Avatar({ username, ...props }: AvatarProps) { 10 | const code = useMemo( 11 | () => (username.length > 0 ? username[0].toUpperCase() : "A"), 12 | [username], 13 | ); 14 | 15 | const background = useMemo(() => { 16 | const colors = [ 17 | "bg-red-500", 18 | "bg-yellow-500", 19 | "bg-green-500", 20 | "bg-blue-500", 21 | "bg-indigo-500", 22 | "bg-purple-500", 23 | "bg-pink-500", 24 | ]; 25 | const index = code.charCodeAt(0) % colors.length; 26 | return colors[index]; 27 | }, [username]); 28 | 29 | return useDeeptrain ? ( 30 | 31 | ) : ( 32 |
33 |

{code}

34 |
35 | ); 36 | } 37 | 38 | export default Avatar; 39 | -------------------------------------------------------------------------------- /app/src/components/Broadcast.tsx: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import { selectInit } from "@/store/auth.ts"; 3 | import { useToast } from "@/components/ui/use-toast.ts"; 4 | import { useTranslation } from "react-i18next"; 5 | import { useEffectAsync } from "@/utils/hook.ts"; 6 | import { getBroadcast } from "@/api/broadcast.ts"; 7 | 8 | function Broadcast() { 9 | const { t } = useTranslation(); 10 | const init = useSelector(selectInit); 11 | const { toast } = useToast(); 12 | useEffectAsync(async () => { 13 | if (!init) return; 14 | 15 | const content = await getBroadcast(); 16 | if (content.length === 0) return; 17 | 18 | toast({ 19 | title: t("broadcast"), 20 | description: content, 21 | duration: 10000, 22 | }); 23 | }, [init]); 24 | 25 | return
; 26 | } 27 | 28 | export default Broadcast; 29 | -------------------------------------------------------------------------------- /app/src/components/I18nProvider.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "./ui/button.tsx"; 2 | import { Languages } from "lucide-react"; 3 | import { 4 | DropdownMenu, 5 | DropdownMenuCheckboxItem, 6 | DropdownMenuContent, 7 | DropdownMenuTrigger, 8 | } from "./ui/dropdown-menu.tsx"; 9 | import { langsProps, setLanguage } from "@/i18n.ts"; 10 | import { useTranslation } from "react-i18next"; 11 | 12 | function I18nProvider() { 13 | const { i18n } = useTranslation(); 14 | 15 | return ( 16 | 17 | 18 | 21 | 22 | 23 | {Object.entries(langsProps).map(([key, value]) => ( 24 | setLanguage(i18n, key)} 28 | > 29 | {value} 30 | 31 | ))} 32 | 33 | 34 | ); 35 | } 36 | 37 | export default I18nProvider; 38 | -------------------------------------------------------------------------------- /app/src/components/Loader.tsx: -------------------------------------------------------------------------------- 1 | import "@/assets/common/loader.less"; 2 | 3 | type LoaderProps = { 4 | className?: string; 5 | prompt?: string; 6 | }; 7 | 8 | function Loader({ className, prompt }: LoaderProps) { 9 | return ( 10 |
11 |
12 |

{prompt}

13 |
14 | ); 15 | } 16 | 17 | export default Loader; 18 | -------------------------------------------------------------------------------- /app/src/components/ProjectLink.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "./ui/button.tsx"; 2 | import { selectMessages } from "@/store/chat.ts"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { MessageSquarePlus } from "lucide-react"; 5 | import { toggleConversation } from "@/api/history.ts"; 6 | import Github from "@/components/ui/icons/Github.tsx"; 7 | 8 | function ProjectLink() { 9 | const dispatch = useDispatch(); 10 | const messages = useSelector(selectMessages); 11 | 12 | return messages.length > 0 ? ( 13 | 20 | ) : ( 21 | 30 | ); 31 | } 32 | 33 | export default ProjectLink; 34 | -------------------------------------------------------------------------------- /app/src/components/ReloadService.tsx: -------------------------------------------------------------------------------- 1 | import { version } from "@/conf"; 2 | import { useTranslation } from "react-i18next"; 3 | import { useToast } from "./ui/use-toast.ts"; 4 | import { getMemory, setMemory } from "@/utils/memory.ts"; 5 | 6 | function ReloadPrompt() { 7 | const { t } = useTranslation(); 8 | const { toast } = useToast(); 9 | 10 | const before = getMemory("version"); 11 | if (before.length > 0 && before !== version) { 12 | setMemory("version", version); 13 | toast({ 14 | title: t("service.update-success"), 15 | description: t("service.update-success-prompt"), 16 | }); 17 | console.debug( 18 | `[service] service worker updated (from ${before} to ${version})`, 19 | ); 20 | } 21 | setMemory("version", version); 22 | 23 | return <>; 24 | } 25 | 26 | export default ReloadPrompt; 27 | -------------------------------------------------------------------------------- /app/src/components/TickButton.tsx: -------------------------------------------------------------------------------- 1 | import { Button, ButtonProps } from "@/components/ui/button.tsx"; 2 | import React, { useEffect, useRef, useState } from "react"; 3 | import { isAsyncFunc } from "@/utils/base.ts"; 4 | 5 | export interface TickButtonProps extends ButtonProps { 6 | tick: number; 7 | onTickChange?: (tick: number) => void; 8 | onClick?: ( 9 | e: React.MouseEvent, 10 | ) => boolean | Promise; 11 | } 12 | 13 | function TickButton({ 14 | tick, 15 | onTickChange, 16 | onClick, 17 | children, 18 | ...props 19 | }: TickButtonProps) { 20 | const stamp = useRef(0); 21 | const [timer, setTimer] = useState(0); 22 | 23 | useEffect(() => { 24 | setInterval(() => { 25 | const offset = Math.floor((Number(Date.now()) - stamp.current) / 1000); 26 | let value = tick - offset; 27 | if (value <= 0) value = 0; 28 | setTimer(value); 29 | onTickChange && onTickChange(value); 30 | }, 250); 31 | }, []); 32 | 33 | const onReset = () => (stamp.current = Number(Date.now())); 34 | 35 | // if is async function, use this: 36 | const onTrigger = isAsyncFunc(onClick) 37 | ? async (e: React.MouseEvent) => { 38 | if (timer !== 0 || !onClick) return; 39 | if (await onClick(e)) onReset(); 40 | } 41 | : (e: React.MouseEvent) => { 42 | if (timer !== 0 || !onClick) return; 43 | if (onClick(e)) onReset(); 44 | }; 45 | 46 | return ( 47 | 50 | ); 51 | } 52 | 53 | export default TickButton; 54 | -------------------------------------------------------------------------------- /app/src/components/home/assemblies/ActionButton.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button.tsx"; 2 | import { PauseCircle } from "lucide-react"; 3 | 4 | type SendButtonProps = { 5 | working: boolean; 6 | onClick: () => any; 7 | }; 8 | 9 | function ActionButton({ onClick, working }: SendButtonProps) { 10 | return ( 11 | 31 | ); 32 | } 33 | 34 | export default ActionButton; 35 | -------------------------------------------------------------------------------- /app/src/components/home/assemblies/ChatInput.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { setMemory } from "@/utils/memory.ts"; 3 | import { useTranslation } from "react-i18next"; 4 | import { Textarea } from "@/components/ui/textarea.tsx"; 5 | import { useSelector } from "react-redux"; 6 | import { senderSelector } from "@/store/settings.ts"; 7 | 8 | type ChatInputProps = { 9 | className?: string; 10 | target?: React.RefObject; 11 | value: string; 12 | onValueChange: (value: string) => void; 13 | onEnterPressed: () => void; 14 | }; 15 | 16 | function ChatInput({ 17 | className, 18 | target, 19 | value, 20 | onValueChange, 21 | onEnterPressed, 22 | }: ChatInputProps) { 23 | const { t } = useTranslation(); 24 | const [pressed, setPressed] = React.useState(false); 25 | const sender = useSelector(senderSelector); 26 | 27 | return ( 28 |