1 Commits
main ... web

Author SHA1 Message Date
Sakurasan
4762ed9841 add web page 2023-05-30 22:40:49 +08:00
37 changed files with 876 additions and 2971 deletions

1
.gitignore vendored
View File

@@ -2,4 +2,3 @@ bin/
test/
*.log
*.db
demo/

View File

@@ -10,15 +10,11 @@ OpenCat for Team的开源实现
~~基本~~实现了opencatd的全部功能
(openai附属能力:whisper,tts,dall-e(text to image)...)
## Extra Support:
| 任务 | 完成情况 |
| --- | --- |
|[Azure OpenAI](./doc/azure.md) | ✅|
|[Claude](./doc/azure.md) | ✅|
|[Gemini](./doc/gemini.md) | ✅|
| ... | ... |
@@ -54,11 +50,6 @@ wget https://github.com/mirrors2/opencatd-open/raw/main/docker/docker-compose.ym
>重置 root 的 token
- `docker exec opencatd-open opencatd reset_root`
>导出 user info -> user.json (docker file path: /app/db/user.json)
- `docker exec opencatd-open opencatd save`
>导入 user.json -> db
- `docker exec opencatd-open opencatd load`
## Q&A
关于证书?
@@ -74,12 +65,6 @@ wget https://github.com/mirrors2/opencatd-open/raw/main/docker/docker-compose.ym
修改openai的endpoint地址使用任意上游地址(套娃代理)
- 设置环境变量 openai_endpoint
使用Nginx + Docker部署
- [使用Nginx + Docker部署](./doc/deploy.md)
pandora for team
- [pandora for team](./doc/pandora.md)
# License
[GNU General Public License v3.0](License)

View File

@@ -115,9 +115,8 @@ Resp:
### 重置用户 Token
- URL: `/1/users/:id/reset`
- URL: `/1/users/:id/reset?token={new user token}`
- Method: `POST`
- Description: 重置用户 Token 默认生成新 Token 也可以指定
- Description: 重置用户 Token
- Headers:
- Authorization: Bearer {token}
@@ -181,7 +180,7 @@ Req:
}
```
api_type:不传的话默认为“openai”;当前可选值[openai,azure,claude]
api_type:不传的话默认为“openai”;当前可选值[openai,azure_openai]
endpoint: 当 api_type 为 azure_openai时传入目前暂未使用
Resp:
@@ -236,7 +235,4 @@ Resp:
"totalUnit" : 55
}
]
```
## Whisper接口
### 与openai一致
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -8,7 +8,6 @@
| model name | deployment name |
| --- | --- |
|gpt-35-turbo | gpt-35-turbo |
|gpt-35-turbo-16k | gpt-35-turbo-16k |
| gpt-4 | gpt-4 |
## How to use
@@ -16,10 +15,3 @@
- key name以 azure.[resource name]的方式添加
- 密钥任取一个
- <img src="./azure_openai_for_team.png" alt="azure_openai_for_team" height="600">
- [AMA(问天)](http://bytemyth.com/ama) 使用方式
- ![](azure_ama.png)
- 每个 team server 用户旁边有一个复制按钮,点击后,把复制的链接粘贴到浏览器,可以一键设置
## Claude
- opencat 添加Claude api, key name以 "claude.key名称",即("Api类型.Key名称")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -1,28 +0,0 @@
# nginx + docker
自行安装相关环境,省事直接用了宝塔面板
docker-compose.yml
```
version: '3.7'
services:
opencatd:
image: mirrors2/opencatd-open
container_name: opencatd-open
restart: unless-stopped
ports:
- 8088:80
volumes:
- $PWD/db:/app/db
```
nginx配置
```
location /
{
proxy_pass http://localhost:8088;
proxy_set_header Host localhost;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
}
```

View File

@@ -1,6 +0,0 @@
## 添加ApiKey
gemini的"ApiType":"google"
或者使用 google.xxxx 的apikey名称 添加
![gemini key](./gemini_key.jpg)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -1,24 +0,0 @@
# pandora for team
[pandora](https://github.com/pengzhile/pandora)是一个把ChatGPT(web/App)接口化的项目,可以看做是第三方 OpenAI API 提供方(接口和OpenAI一致)
## 准备
- https://ai.fakeopen.com/auth1 获取accesstoken
- https://ai.fakeopen.com/token 创建apikey
## 客户端设置
1.添加接口
![](ama_pandora_provider.png)
2.创建用户&Copy Config
![](ama_pandora_copy_config.png)
Ex:`ama://set-api-key?server=http%3A%2F%2F123.456.7.89&key=8fc322fa-15d2-43d7-bc59-621554e82c2a`
3.Configure Client
![](ama_pandora_config_client.png)
3.测试聊天
![](ama_pandora_chat_test.png)

View File

@@ -3,7 +3,7 @@ WORKDIR /frontend-build
COPY ./web/ .
RUN npm install && npm run build && rm -rf node_modules
FROM golang:1.21-alpine as builder
FROM golang:1.19-alpine as builder
LABEL anther="github.com/Sakurasan"
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && apk --no-cache add make cmake upx
WORKDIR /build
@@ -11,13 +11,13 @@ COPY --from=frontend /frontend-build/dist /build/dist
COPY . /build
ENV GO111MODULE=on
# ENV GOPROXY=https://goproxy.cn,direct
CMD [ "go mod tidy","go mod download" ]
CMD [ "go mod download" ]
RUN make build
FROM alpine:latest AS runner
# 设置alpine 时间为上海时间
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && apk update && apk --no-cache add tzdata ffmpeg && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && apk update && apk --no-cache add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
# RUN apk update && apk --no-cache add openssl libgcc libstdc++ binutils
WORKDIR /app
COPY --from=builder /build/bin/opencatd /app/opencatd

113
go.mod
View File

@@ -1,102 +1,51 @@
module opencatd-open
go 1.21
toolchain go1.21.9
go 1.19
require (
cloud.google.com/go/vertexai v0.7.1
github.com/Sakurasan/to v0.0.0-20180919163141-e72657dd7c7d
github.com/duke-git/lancet/v2 v2.3.0
github.com/faiface/beep v1.1.0
github.com/gin-gonic/gin v1.10.0
github.com/glebarez/sqlite v1.11.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/generative-ai-go v0.12.0
github.com/google/uuid v1.6.0
github.com/duke-git/lancet/v2 v2.1.19
github.com/gin-gonic/gin v1.9.0
github.com/glebarez/sqlite v1.7.0
github.com/google/uuid v1.3.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkoukk/tiktoken-go v0.1.6
github.com/robfig/cron/v3 v3.0.1
github.com/sashabaranov/go-openai v1.23.1
github.com/tidwall/gjson v1.17.1
golang.org/x/oauth2 v0.20.0
google.golang.org/api v0.180.0
gopkg.in/vansante/go-ffprobe.v2 v2.1.1
gorm.io/gorm v1.25.10
github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480
github.com/sashabaranov/go-openai v1.9.0
gorm.io/gorm v1.24.6
)
require (
cloud.google.com/go v0.113.0 // indirect
cloud.google.com/go/ai v0.5.0 // indirect
cloud.google.com/go/aiplatform v1.66.0 // indirect
cloud.google.com/go/auth v0.4.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
cloud.google.com/go/compute v1.26.0 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
cloud.google.com/go/iam v1.1.7 // indirect
cloud.google.com/go/longrunning v0.5.7 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dlclark/regexp2 v1.11.0 // indirect
github.com/bytedance/sonic v1.8.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/dlclark/regexp2 v1.8.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/glebarez/go-sqlite v1.20.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.4 // indirect
github.com/hajimehoshi/go-mp3 v0.3.4 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
go.opentelemetry.io/otel v1.26.0 // indirect
go.opentelemetry.io/otel/metric v1.26.0 // indirect
go.opentelemetry.io/otel/trace v1.26.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.50.5 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/sqlite v1.29.9 // indirect
modernc.org/libc v1.22.2 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.20.3 // indirect
)

470
go.sum
View File

@@ -1,435 +1,139 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
cloud.google.com/go v0.112.2 h1:ZaGT6LiG7dBzi6zNOvVZwacaXlmf3lRqnC4DQzqyRQw=
cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms=
cloud.google.com/go v0.113.0 h1:g3C70mn3lWfckKBiCVsAshabrDg01pQ0pnX1MNtnMkA=
cloud.google.com/go v0.113.0/go.mod h1:glEqlogERKYeePz6ZdkcLJ28Q2I6aERgDDErBg9GzO8=
cloud.google.com/go/ai v0.3.5-0.20240409161017-ce55ad694f21 h1:kSJt55RNa+qATWnX2xjyq9S2YGDxxBwpmUVZNuFLOi0=
cloud.google.com/go/ai v0.3.5-0.20240409161017-ce55ad694f21/go.mod h1:iX72tmUodGXVDxRDCGUZEPiB9HaMeERXkOdgCkUi8sA=
cloud.google.com/go/ai v0.4.1 h1:Rk+AZuehuKoA5E0FccwhU6JB3CPpsC9VUbsOMj8KxDk=
cloud.google.com/go/ai v0.4.1/go.mod h1:vJ7QwLSfisbbZAHFxiaPX8+g9Kuxt1DjXmGrEujThwU=
cloud.google.com/go/ai v0.5.0 h1:x8s4rDn5t9OVZvBCgtr5bZTH5X0O7JdE6zYo+O+MpRw=
cloud.google.com/go/ai v0.5.0/go.mod h1:96VBphk70e0zdXZrbtgPuKYRZsQ3UktSUXhuojwiKA8=
cloud.google.com/go/aiplatform v1.60.0 h1:0cSrii1ZeLr16MbBoocyy5KVnrSdiQ3KN/vtrTe7RqE=
cloud.google.com/go/aiplatform v1.60.0/go.mod h1:eTlGuHOahHprZw3Hio5VKmtThIOak5/qy6pzdsqcQnM=
cloud.google.com/go/aiplatform v1.66.0 h1:bbFYY4JInclG10czRFUYj2rjD+obhh3Gi9zVlyoMgEc=
cloud.google.com/go/aiplatform v1.66.0/go.mod h1:bPQS0UjaXaTAq57UgP3XWDCtYFOIbXXpkMsl6uP4JAc=
cloud.google.com/go/auth v0.4.0 h1:vcJWEguhY8KuiHoSs/udg1JtIRYm3YAWPBE1moF1m3U=
cloud.google.com/go/auth v0.4.0/go.mod h1:tO/chJN3obc5AbRYFQDsuFbL4wW5y8LfbPtDCfgwOVE=
cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40=
cloud.google.com/go/compute v1.26.0 h1:uHf0NN2nvxl1Gh4QO83yRCOdMK4zivtMS5gv0dEX0hg=
cloud.google.com/go/compute v1.26.0/go.mod h1:T9RIRap4pVHCGUkVFRJ9hygT3KCXjip41X1GgWtBBII=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc=
cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI=
cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA=
cloud.google.com/go/longrunning v0.5.6 h1:xAe8+0YaWoCKr9t1+aWe+OeQgN/iJK1fEgZSXmjuEaE=
cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA=
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
cloud.google.com/go/vertexai v0.7.1 h1:CSdqsEwjklLIlI1e5SrsnkwG/I+CeJekkBbMTzeYhVg=
cloud.google.com/go/vertexai v0.7.1/go.mod h1:HfnfYR9aPS+qF2436S6Hzuw0Fp+PORjzK3ggqymdzSU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/Sakurasan/to v0.0.0-20180919163141-e72657dd7c7d h1:3v1QFdgk450QH+7C+lw1k+olbjK4fKGsrEfnEG/HLkY=
github.com/Sakurasan/to v0.0.0-20180919163141-e72657dd7c7d/go.mod h1:2sp0vsMyh5sqmKl5N+ps/cSspqLkoXUlesSzsufIGRU=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/duke-git/lancet/v2 v2.2.7 h1:u9zr6HR+MDUvZEtTlAFtSTIgZfEFsN7cKi27n5weZsw=
github.com/duke-git/lancet/v2 v2.2.7/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
github.com/duke-git/lancet/v2 v2.3.0 h1:Ztie0qOnC4QgGYYqmpmQxbxkPcm54kqFXj1bwhiV8zg=
github.com/duke-git/lancet/v2 v2.3.0/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0=
github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/duke-git/lancet/v2 v2.1.19 h1:dbRB1m6wOMV1I0ax/3S6ngop8SYM6I7sr+7D9IXjS2E=
github.com/duke-git/lancet/v2 v2.1.19/go.mod h1:hNcc06mV7qr+crH/0nP+rlC3TB0Q9g5OrVnO8/TGD4c=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c=
github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=
github.com/glebarez/sqlite v1.7.0 h1:A7Xj/KN2Lvie4Z4rrgQHY8MsbebX3NyWsL3n2i82MVI=
github.com/glebarez/sqlite v1.7.0/go.mod h1:PkeevrRlF/1BhQBCnzcMWzgrIk7IOop+qS2jUYLfHhk=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/generative-ai-go v0.11.0 h1:+wL9xu5jVIgJKC6NmZOxZsBYWDtIap7DGUZ1diQSSnk=
github.com/google/generative-ai-go v0.11.0/go.mod h1:RauvbBjc+AzW0b1LV0VSlxHI5n2i3dz8oJfjboOSiWQ=
github.com/google/generative-ai-go v0.11.2 h1:T/Liv2wfg+l3pYVuL9lOx3tsvVXzby/jwiUCxncRyag=
github.com/google/generative-ai-go v0.11.2/go.mod h1:gk9K/raHyN6r8vdbaNDn9X6GXzeE78iMjcrWm6a5x/Q=
github.com/google/generative-ai-go v0.12.0 h1:ocoAhazDpxDYgjTZdQ2aeVG+Sz4lvmhzfAlRRQF+mxU=
github.com/google/generative-ai-go v0.12.0/go.mod h1:ZTE7C93HuLGT6oJ1IJGt8dfo7HCHqBv3dVUGUCns0yE=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA=
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68=
github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo=
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo=
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAcUsw=
github.com/pkoukk/tiktoken-go v0.1.6/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480 h1:IFhPCcB0/HtnEN+ZoUGDT55YgFCymbFJ15kXqs3nv5w=
github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480/go.mod h1:BijIqAP84FMYC4XbdJgjyMpiSjusU8x0Y0W9K2t0QtU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/sashabaranov/go-openai v1.17.9 h1:QEoBiGKWW68W79YIfXWEFZ7l5cEgZBV4/Ow3uy+5hNY=
github.com/sashabaranov/go-openai v1.17.9/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sashabaranov/go-openai v1.23.1 h1:b2IsEG9+BdJ3f6G3gGu9Lon2Mw/C0aYqME3YzwBHcls=
github.com/sashabaranov/go-openai v1.23.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 h1:VstopitMQi3hZP0fzvnsLmzXZdQGc4bEcgu24cp+d4M=
github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/sashabaranov/go-openai v1.9.0 h1:NoiO++IISxxJ1pRc0n7uZvMGMake0G+FJ1XPwXtprsA=
github.com/sashabaranov/go-openai v1.9.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc=
golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No=
golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.173.0 h1:fz6B7GWYWLS/HfruiTsRYVKQQApJ6vasTYWAK6+Qo8g=
google.golang.org/api v0.173.0/go.mod h1:ins7pTzjeBPQ3SdC/plzki6d/dQWwAWy8qVZ4Vgkzl8=
google.golang.org/api v0.178.0 h1:yoW/QMI4bRVCHF+NWOTa4cL8MoWL3Jnuc7FlcFF91Ok=
google.golang.org/api v0.178.0/go.mod h1:84/k2v8DFpDRebpGcooklv/lais3MEfqpaBLA12gl2U=
google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4=
google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw=
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw=
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda h1:b6F6WIV4xHHD0FA4oIyzU6mHWg2WI2X1RBehwa5QN38=
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda/go.mod h1:AHcE/gZH76Bk/ROZhQphlRoWo5xKDEtz3eVEO1LfA8c=
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk=
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y=
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No=
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa h1:RBgMaUMP+6soRkik4VoN8ojR2nex2TqZwjSSogic+eo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240506185236-b8a5c65736ae h1:c55+MER4zkBS14uJhSZMGGmya0yJx5iHV4x/fpOSNRk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/vansante/go-ffprobe.v2 v2.1.1 h1:DIh5fMn+tlBvG7pXyUZdemVmLdERnf2xX6XOFF+0BBU=
gopkg.in/vansante/go-ffprobe.v2 v2.1.1/go.mod h1:qF0AlAjk7Nqzqf3y333Ly+KxN3cKF2JqA3JT5ZheUGE=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
modernc.org/libc v1.35.0 h1:EQ4szx6Q/QLZuysmAnI4dfRnKbAbNlENp23ruvTJ2nE=
modernc.org/libc v1.35.0/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/libc v1.50.5 h1:ZzeUd0dIc/sUtoPTCYIrgypkuzoGzNu6kbEWj2VuEmk=
modernc.org/libc v1.50.5/go.mod h1:rhzrUx5oePTSTIzBgM0mTftwWHK8tiT9aNFUt1mldl0=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8=
modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
modernc.org/sqlite v1.29.9 h1:9RhNMklxJs+1596GNuAX+O/6040bvOwacTxuFcRuQow=
modernc.org/sqlite v1.29.9/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
gorm.io/gorm v1.24.6 h1:wy98aq9oFEetsc4CAbKD2SoBCdMzsbSIvSUUFJuHi5s=
gorm.io/gorm v1.24.6/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.20.3 h1:SqGJMMxjj1PHusLxdYxeQSodg7Jxn9WWkaAQjKrntZs=
modernc.org/sqlite v1.20.3/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@@ -1,10 +1,7 @@
package main
import (
"bytes"
"embed"
"encoding/json"
"fmt"
"io/fs"
"log"
"net/http"
@@ -12,7 +9,6 @@ import (
"opencatd-open/store"
"os"
"github.com/duke-git/lancet/v2/fileutil"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"gorm.io/gorm"
@@ -33,12 +29,6 @@ func getFileSystem(path string) http.FileSystem {
func main() {
args := os.Args[1:]
if len(args) > 0 {
type user struct {
ID uint
Name string
Token string
}
var us []user
switch args[0] {
case "reset_root":
log.Println("reset root token...")
@@ -56,84 +46,17 @@ func main() {
log.Fatalln(err)
return
}
log.Println("[success]new root token:", ntoken)
log.Println("new root token:", ntoken)
return
case "root_token":
log.Println("query root token...")
log.Println("reset root token...")
if user, err := store.GetUserByID(uint(1)); err != nil {
log.Fatalln(err)
return
} else {
log.Println("[success]root token:", user.Token)
log.Println("root token:", user.Token)
return
}
case "save":
log.Println("backup user info -> user.json")
if users, err := store.GetAllUsers(); err != nil {
log.Fatalln(err)
return
} else {
for _, u := range users {
us = append(us, user{ID: u.ID, Name: u.Name, Token: u.Token})
}
}
if !fileutil.IsExist("./db/user.json") {
file, err := os.Create("./db/user.json")
if err != nil {
log.Fatalln(err)
return
}
defer file.Close()
} else {
// 文件存在,打开文件
file, _ := os.OpenFile("./db/user.json", os.O_RDWR|os.O_TRUNC, 0666)
defer file.Close()
buff := bytes.NewBuffer(nil)
json.NewEncoder(buff).Encode(us)
file.WriteString(buff.String())
fmt.Println("------- END -------")
return
}
case "load":
fmt.Println("\nimport user.json -> db")
if !fileutil.IsExist("./db/user.json") {
log.Fatalln("404! user.json is not found.")
return
}
users, err := store.GetAllUsers()
if err != nil {
log.Println(err)
return
}
if len(users) != 0 {
log.Println("user db 存在数据,取消导入")
return
}
file, err := os.Open("./db/user.json")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
decoder := json.NewDecoder(file)
err = decoder.Decode(&us)
if err != nil {
fmt.Println("Error decoding JSON:", err)
return
}
for _, u := range us {
log.Println(u.ID, u.Name, u.Token)
err := store.CreateUser(&store.User{ID: u.ID, Name: u.Name, Token: u.Token})
if err != nil {
log.Println(err)
}
}
fmt.Println("------- END -------")
return
default:
return
}
@@ -148,8 +71,6 @@ func main() {
// 获取当前用户信息
group.GET("/me", router.HandleMe)
group.GET("/me/usages", router.HandleMeUsage)
// 获取所有Key
group.GET("/keys", router.HandleKeys)

View File

@@ -1,6 +1,5 @@
/*
https://learn.microsoft.com/zh-cn/azure/cognitive-services/openai/chatgpt-quickstart
https://learn.microsoft.com/zh-cn/azure/ai-services/openai/reference#chat-completions
curl $AZURE_OPENAI_ENDPOINT/openai/deployments/gpt-35-turbo/chat/completions?api-version=2023-03-15-preview \
-H "Content-Type: application/json" \
@@ -10,15 +9,10 @@ curl $AZURE_OPENAI_ENDPOINT/openai/deployments/gpt-35-turbo/chat/completions?api
"messages": [{"role": "user", "content": "你好"}]
}'
https://learn.microsoft.com/zh-cn/rest/api/cognitiveservices/azureopenaistable/models/list?tabs=HTTP
curl $AZURE_OPENAI_ENDPOINT/openai/deployments?api-version=2022-12-01 \
-H "Content-Type: application/json" \
-H "api-key: $AZURE_OPENAI_KEY" \
> GPT-4 Turbo
https://techcommunity.microsoft.com/t5/ai-azure-ai-services-blog/azure-openai-service-launches-gpt-4-turbo-and-gpt-3-5-turbo-1106/ba-p/3985962
*/
package azureopenai

View File

@@ -1,284 +0,0 @@
// https://docs.anthropic.com/claude/reference/messages_post
package claude
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"opencatd-open/pkg/openai"
"opencatd-open/pkg/tokenizer"
"opencatd-open/store"
"strings"
"github.com/gin-gonic/gin"
)
func ChatProxy(c *gin.Context, chatReq *openai.ChatCompletionRequest) {
ChatMessages(c, chatReq)
}
func ChatTextCompletions(c *gin.Context, chatReq *openai.ChatCompletionRequest) {
}
type ChatRequest struct {
Model string `json:"model,omitempty"`
Messages any `json:"messages,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
Stream bool `json:"stream,omitempty"`
System string `json:"system,omitempty"`
TopK int `json:"top_k,omitempty"`
TopP float64 `json:"top_p,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
}
func (c *ChatRequest) ByteJson() []byte {
bytejson, _ := json.Marshal(c)
return bytejson
}
type ChatMessage struct {
Role string `json:"role,omitempty"`
Content string `json:"content,omitempty"`
}
type VisionMessages struct {
Role string `json:"role,omitempty"`
Content []VisionContent `json:"content,omitempty"`
}
type VisionContent struct {
Type string `json:"type,omitempty"`
Source *VisionSource `json:"source,omitempty"`
Text string `json:"text,omitempty"`
}
type VisionSource struct {
Type string `json:"type,omitempty"`
MediaType string `json:"media_type,omitempty"`
Data string `json:"data,omitempty"`
}
type ChatResponse struct {
ID string `json:"id"`
Type string `json:"type"`
Role string `json:"role"`
Model string `json:"model"`
StopSequence any `json:"stop_sequence"`
Usage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
} `json:"usage"`
Content []struct {
Type string `json:"type"`
Text string `json:"text"`
} `json:"content"`
StopReason string `json:"stop_reason"`
}
type ClaudeStreamResponse struct {
Type string `json:"type"`
Index int `json:"index"`
ContentBlock struct {
Type string `json:"type"`
Text string `json:"text"`
} `json:"content_block"`
Delta struct {
Type string `json:"type"`
Text string `json:"text"`
StopReason string `json:"stop_reason"`
StopSequence any `json:"stop_sequence"`
} `json:"delta"`
Message struct {
ID string `json:"id"`
Type string `json:"type"`
Role string `json:"role"`
Content []any `json:"content"`
Model string `json:"model"`
StopReason string `json:"stop_reason"`
StopSequence any `json:"stop_sequence"`
Usage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
} `json:"usage"`
} `json:"message"`
Error struct {
Type string `json:"type"`
Message string `json:"message"`
} `json:"error"`
Usage struct {
OutputTokens int `json:"output_tokens"`
} `json:"usage"`
}
func ChatMessages(c *gin.Context, chatReq *openai.ChatCompletionRequest) {
onekey, err := store.SelectKeyCache("claude")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
usagelog := store.Tokens{Model: chatReq.Model}
var claudReq ChatRequest
claudReq.Model = chatReq.Model
claudReq.Stream = chatReq.Stream
// claudReq.Temperature = chatReq.Temperature
claudReq.TopP = chatReq.TopP
claudReq.MaxTokens = 4096
var prompt string
var claudecontent []VisionContent
for _, msg := range chatReq.Messages {
if msg.Role == "system" {
claudReq.System = string(msg.Content)
continue
}
var oaivisioncontent []openai.VisionContent
if err := json.Unmarshal(msg.Content, &oaivisioncontent); err != nil {
prompt += "<" + msg.Role + ">: " + string(msg.Content) + "\n"
claudecontent = append(claudecontent, VisionContent{Type: "text", Text: msg.Role + ":" + string(msg.Content)})
} else {
if len(oaivisioncontent) > 0 {
for _, content := range oaivisioncontent {
if content.Type == "text" {
prompt += "<" + msg.Role + ">: " + content.Text + "\n"
claudecontent = append(claudecontent, VisionContent{Type: "text", Text: msg.Role + ":" + content.Text})
} else if content.Type == "image_url" {
if strings.HasPrefix(content.ImageURL.URL, "http") {
fmt.Println("链接:", content.ImageURL.URL)
} else if strings.HasPrefix(content.ImageURL.URL, "data:image") {
fmt.Println("base64:", content.ImageURL.URL[:20])
}
// todo image tokens
var mediaType string
if strings.HasPrefix(content.ImageURL.URL, "data:image/jpeg") {
mediaType = "image/jpeg"
}
if strings.HasPrefix(content.ImageURL.URL, "data:image/png") {
mediaType = "image/png"
}
claudecontent = append(claudecontent, VisionContent{Type: "image", Source: &VisionSource{Type: "base64", MediaType: mediaType, Data: strings.Split(content.ImageURL.URL, ",")[1]}})
}
}
}
}
// if len(chatReq.Tools) > 0 {
// tooljson, _ := json.Marshal(chatReq.Tools)
// prompt += "<tools>: " + string(tooljson) + "\n"
// }
}
claudReq.Messages = []VisionMessages{{Role: "user", Content: claudecontent}}
usagelog.PromptCount = tokenizer.NumTokensFromStr(prompt, chatReq.Model)
req, _ := http.NewRequest("POST", MessageEndpoint, bytes.NewReader(claudReq.ByteJson()))
req.Header.Set("x-api-key", onekey.Key)
req.Header.Set("anthropic-version", "2023-06-01")
req.Header.Set("Content-Type", "application/json")
client := http.DefaultClient
rsp, err := client.Do(req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
io.Copy(c.Writer, rsp.Body)
return
}
var buffer bytes.Buffer
teeReader := io.TeeReader(rsp.Body, &buffer)
dataChan := make(chan string)
// stopChan := make(chan bool)
var result string
scanner := bufio.NewScanner(teeReader)
go func() {
for scanner.Scan() {
line := scanner.Bytes()
if len(line) > 0 && bytes.HasPrefix(line, []byte("data: ")) {
if bytes.HasPrefix(line, []byte("data: [DONE]")) {
dataChan <- string(line) + "\n"
break
}
var claudeResp ClaudeStreamResponse
line = bytes.Replace(line, []byte("data: "), []byte(""), -1)
line = bytes.TrimSpace(line)
if err := json.Unmarshal(line, &claudeResp); err != nil {
continue
}
if claudeResp.Type == "message_start" {
if claudeResp.Message.Role != "" {
result += "<" + claudeResp.Message.Role + ">"
}
} else if claudeResp.Type == "message_stop" {
break
}
if claudeResp.Delta.Text != "" {
result += claudeResp.Delta.Text
}
var choice openai.Choice
choice.Delta.Role = claudeResp.Message.Role
choice.Delta.Content = claudeResp.Delta.Text
choice.FinishReason = claudeResp.Delta.StopReason
chatResp := openai.ChatCompletionStreamResponse{
Model: chatReq.Model,
Choices: []openai.Choice{choice},
}
dataChan <- "data: " + string(chatResp.ByteJson()) + "\n"
if claudeResp.Delta.StopReason != "" {
dataChan <- "\ndata: [DONE]\n"
}
}
}
defer close(dataChan)
}()
c.Writer.Header().Set("Content-Type", "text/event-stream")
c.Writer.Header().Set("Cache-Control", "no-cache")
c.Writer.Header().Set("Connection", "keep-alive")
c.Writer.Header().Set("Transfer-Encoding", "chunked")
c.Writer.Header().Set("X-Accel-Buffering", "no")
c.Stream(func(w io.Writer) bool {
if data, ok := <-dataChan; ok {
if strings.HasPrefix(data, "data: ") {
c.Writer.WriteString(data)
// c.Writer.WriteString("\n\n")
} else {
c.Writer.WriteHeader(http.StatusBadGateway)
c.Writer.WriteString(data)
}
c.Writer.Flush()
return true
}
go func() {
usagelog.CompletionCount = tokenizer.NumTokensFromStr(result, chatReq.Model)
usagelog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(usagelog.Model, usagelog.PromptCount, usagelog.CompletionCount))
if err := store.Record(&usagelog); err != nil {
log.Println(err)
}
if err := store.SumDaily(usagelog.UserID); err != nil {
log.Println(err)
}
}()
return false
})
}

View File

@@ -1,429 +0,0 @@
/*
https://docs.anthropic.com/claude/reference/complete_post
curl --request POST \
--url https://api.anthropic.com/v1/complete \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--data '
{
"model": "claude-2",
"prompt": "\n\nHuman: Hello, world!\n\nAssistant:",
"max_tokens_to_sample": 256,
"stream": true
}
'
{"completion":" Hello! Nice to meet you.","stop_reason":"stop_sequence","model":"claude-2.0","stop":"\n\nHuman:","log_id":"727bded01002627057967d02b3d557a01aa73266849b62f5aa0b97dec1247ed3"}
event: completion
data: {"completion":"","stop_reason":"stop_sequence","model":"claude-2.0","stop":"\n\nHuman:","log_id":"dfd42341ad08856ff01811885fb8640a1bf977551d8331f81fe9a6c8182c6c63"}
# Model Pricing
Claude Instant |100,000 tokens |Prompt $1.63/million tokens |Completion $5.51/million tokens
Claude 2 |100,000 tokens |Prompt $11.02/million tokens |Completion $32.68/million tokens
*Claude 1 is still accessible and offered at the same price as Claude 2.
# AWS
https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-service.html
https://aws.amazon.com/cn/bedrock/pricing/
Anthropic models Price for 1000 input tokens Price for 1000 output tokens
Claude Instant $0.00163 $0.00551
Claude $0.01102 $0.03268
https://docs.aws.amazon.com/bedrock/latest/userguide/endpointsTable.html
地区名称 地区 端点 协议
美国东部(弗吉尼亚北部) 美国东部1 bedrock-runtime.us-east-1.amazonaws.com HTTPS
bedrock-runtime-fips.us-east-1.amazonaws.com HTTPS
美国西部(俄勒冈州) 美国西2号 bedrock-runtime.us-west-2.amazonaws.com HTTPS
bedrock-runtime-fips.us-west-2.amazonaws.com HTTPS
亚太地区(新加坡) ap-东南-1 bedrock-runtime.ap-southeast-1.amazonaws.com HTTPS
*/
// package anthropic
package claude
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/http/httputil"
"net/url"
"opencatd-open/pkg/tokenizer"
"opencatd-open/store"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/sashabaranov/go-openai"
)
var (
ClaudeUrl = "https://api.anthropic.com/v1/complete"
MessageEndpoint = "https://api.anthropic.com/v1/messages"
)
type MessageModule struct {
Assistant string // returned data (do not modify)
Human string // input content
}
type CompleteRequest struct {
Model string `json:"model,omitempty"` //*
Prompt string `json:"prompt,omitempty"` //*
MaxTokensToSample int `json:"max_tokens_to_sample,omitempty"` //*
StopSequences string `json:"stop_sequences,omitempty"`
Temperature int `json:"temperature,omitempty"`
TopP int `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"`
Stream bool `json:"stream,omitempty"`
Metadata struct {
UserId string `json:"user_Id,omitempty"`
} `json:"metadata,omitempty"`
}
type CompleteResponse struct {
Completion string `json:"completion"`
StopReason string `json:"stop_reason"`
Model string `json:"model"`
Stop string `json:"stop"`
LogID string `json:"log_id"`
}
func Create() {
complet := CompleteRequest{
Model: "claude-2",
Prompt: "Human: Hello, world!\\n\\nAssistant:",
Stream: true,
}
var payload *bytes.Buffer
json.NewEncoder(payload).Encode(complet)
// payload := strings.NewReader("{\"model\":\"claude-2\",\"prompt\":\"\\n\\nHuman: Hello, world!\\n\\nAssistant:\",\"max_tokens_to_sample\":256}")
req, _ := http.NewRequest("POST", ClaudeUrl, payload)
req.Header.Add("accept", "application/json")
req.Header.Add("anthropic-version", "2023-06-01")
req.Header.Add("x-api-key", "$ANTHROPIC_API_KEY")
req.Header.Add("content-type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
// body, _ := io.ReadAll(res.Body)
// fmt.Println(string(body))
reader := bufio.NewReader(res.Body)
for {
line, err := reader.ReadString('\n')
if err == nil {
if strings.HasPrefix(line, "data:") {
fmt.Println(line)
// var result CompleteResponse
// json.Unmarshal()
} else {
continue
}
} else {
break
}
}
}
func ClaudeProxy(c *gin.Context) {
var chatlog store.Tokens
var complete CompleteRequest
byteBody, _ := io.ReadAll(c.Request.Body)
c.Request.Body = io.NopCloser(bytes.NewBuffer(byteBody))
if err := json.Unmarshal(byteBody, &complete); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
key, err := store.SelectKeyCache("claude") //anthropic
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{
"message": err.Error(),
},
})
return
}
chatlog.Model = complete.Model
token, _ := c.Get("localuser")
lu, err := store.GetUserByToken(token.(string))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{
"message": err.Error(),
},
})
return
}
chatlog.UserID = int(lu.ID)
chatlog.PromptCount = tokenizer.NumTokensFromStr(complete.Prompt, complete.Model)
if key.EndPoint == "" {
key.EndPoint = "https://api.anthropic.com"
}
targetUrl, _ := url.ParseRequestURI(key.EndPoint + c.Request.URL.String())
proxy := httputil.NewSingleHostReverseProxy(targetUrl)
proxy.Director = func(req *http.Request) {
req.Host = targetUrl.Host
req.URL.Scheme = targetUrl.Scheme
req.URL.Host = targetUrl.Host
req.Header.Set("anthropic-version", "2023-06-01")
req.Header.Set("content-type", "application/json")
req.Header.Set("x-api-key", key.Key)
}
proxy.ModifyResponse = func(resp *http.Response) error {
if resp.StatusCode != http.StatusOK {
return nil
}
var byteResp []byte
byteResp, _ = io.ReadAll(resp.Body)
resp.Body = io.NopCloser(bytes.NewBuffer(byteResp))
if complete.Stream != true {
var complete_resp CompleteResponse
if err := json.Unmarshal(byteResp, &complete_resp); err != nil {
log.Println(err)
return nil
}
chatlog.CompletionCount = tokenizer.NumTokensFromStr(complete_resp.Completion, chatlog.Model)
} else {
var completion string
for {
line, err := bufio.NewReader(bytes.NewBuffer(byteResp)).ReadString('\n')
if err != nil {
if strings.HasPrefix(line, "data:") {
line = strings.TrimSpace(strings.TrimPrefix(line, "data:"))
if strings.HasSuffix(line, "[DONE]") {
break
}
line = strings.TrimSpace(line)
var complete_resp CompleteResponse
if err := json.Unmarshal([]byte(line), &complete_resp); err != nil {
log.Println(err)
break
}
completion += line
}
}
}
log.Println("completion:", completion)
chatlog.CompletionCount = tokenizer.NumTokensFromStr(completion, chatlog.Model)
}
// calc cost
chatlog.TotalTokens = chatlog.PromptCount + chatlog.CompletionCount
chatlog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
if err := store.Record(&chatlog); err != nil {
log.Println(err)
}
if err := store.SumDaily(chatlog.UserID); err != nil {
log.Println(err)
}
return nil
}
proxy.ServeHTTP(c.Writer, c.Request)
}
func TransReq(chatreq *openai.ChatCompletionRequest) (*bytes.Buffer, error) {
transReq := CompleteRequest{
Model: chatreq.Model,
Temperature: int(chatreq.Temperature),
TopP: int(chatreq.TopP),
Stream: chatreq.Stream,
MaxTokensToSample: chatreq.MaxTokens,
}
if transReq.MaxTokensToSample == 0 {
transReq.MaxTokensToSample = 100000
}
var prompt string
for _, msg := range chatreq.Messages {
switch msg.Role {
case "system":
prompt += fmt.Sprintf("\n\nHuman:%s", msg.Content)
case "user":
prompt += fmt.Sprintf("\n\nHuman:%s", msg.Content)
case "assistant":
prompt += fmt.Sprintf("\n\nAssistant:%s", msg.Content)
}
}
transReq.Prompt = prompt + "\n\nAssistant:"
var payload = bytes.NewBuffer(nil)
if err := json.NewEncoder(payload).Encode(transReq); err != nil {
return nil, err
}
return payload, nil
}
func TransRsp(c *gin.Context, isStream bool, chatlog store.Tokens, reader *bufio.Reader) {
if !isStream {
var completersp CompleteResponse
var chatrsp openai.ChatCompletionResponse
json.NewDecoder(reader).Decode(&completersp)
chatrsp.Model = completersp.Model
chatrsp.ID = completersp.LogID
chatrsp.Object = "chat.completion"
chatrsp.Created = time.Now().Unix()
choice := openai.ChatCompletionChoice{
Index: 0,
FinishReason: "stop",
Message: openai.ChatCompletionMessage{
Role: "assistant",
Content: completersp.Completion,
},
}
chatrsp.Choices = append(chatrsp.Choices, choice)
var payload *bytes.Buffer
if err := json.NewEncoder(payload).Encode(chatrsp); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{
"message": err.Error(),
},
})
return
}
chatlog.CompletionCount = tokenizer.NumTokensFromStr(completersp.Completion, chatlog.Model)
chatlog.TotalTokens = chatlog.PromptCount + chatlog.CompletionCount
chatlog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
if err := store.Record(&chatlog); err != nil {
log.Println(err)
}
if err := store.SumDaily(chatlog.UserID); err != nil {
log.Println(err)
}
c.JSON(http.StatusOK, payload)
return
} else {
var (
wg sync.WaitGroup
dataChan = make(chan string)
stopChan = make(chan bool)
complete_resp string
)
wg.Add(2)
go func() {
defer wg.Done()
for {
line, err := reader.ReadString('\n')
if err == nil {
if strings.HasPrefix(line, "data: ") {
var result CompleteResponse
json.NewDecoder(strings.NewReader(line[6:])).Decode(&result)
if result.StopReason == "" {
if result.Completion != "" {
complete_resp += result.Completion
chatrsp := openai.ChatCompletionStreamResponse{
ID: result.LogID,
Model: result.Model,
Object: "chat.completion",
Created: time.Now().Unix(),
}
choice := openai.ChatCompletionStreamChoice{
Delta: openai.ChatCompletionStreamChoiceDelta{
Role: "assistant",
Content: result.Completion,
},
FinishReason: "",
}
chatrsp.Choices = append(chatrsp.Choices, choice)
bytedate, _ := json.Marshal(chatrsp)
dataChan <- string(bytedate)
}
} else {
chatrsp := openai.ChatCompletionStreamResponse{
ID: result.LogID,
Model: result.Model,
Object: "chat.completion",
Created: time.Now().Unix(),
}
choice := openai.ChatCompletionStreamChoice{
Delta: openai.ChatCompletionStreamChoiceDelta{
Role: "assistant",
Content: result.Completion,
},
}
choice.FinishReason = openai.FinishReason(TranslatestopReason(result.StopReason))
chatrsp.Choices = append(chatrsp.Choices, choice)
bytedate, _ := json.Marshal(chatrsp)
dataChan <- string(bytedate)
dataChan <- "[DONE]"
break
}
} else {
continue
}
} else {
break
}
}
close(dataChan)
stopChan <- true
close(stopChan)
}()
go func() {
defer wg.Done()
Loop:
for {
select {
case data := <-dataChan:
if data != "" {
c.Writer.WriteString("data: " + data)
c.Writer.WriteString("\n\n")
c.Writer.Flush()
}
case <-stopChan:
break Loop
}
}
}()
wg.Wait()
chatlog.CompletionCount = tokenizer.NumTokensFromStr(complete_resp, chatlog.Model)
chatlog.TotalTokens = chatlog.PromptCount + chatlog.CompletionCount
chatlog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
if err := store.Record(&chatlog); err != nil {
log.Println(err)
}
if err := store.SumDaily(chatlog.UserID); err != nil {
log.Println(err)
}
}
}
// claude -> openai
func TranslatestopReason(reason string) string {
switch reason {
case "stop_sequence":
return "stop"
case "max_tokens":
return "length"
default:
return reason
}
}

View File

@@ -1,244 +0,0 @@
package google
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"opencatd-open/pkg/openai"
"opencatd-open/pkg/tokenizer"
"opencatd-open/store"
"strings"
"github.com/gin-gonic/gin"
"github.com/google/generative-ai-go/genai"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
)
type GeminiChatRequest struct {
Contents []GeminiContent `json:"contents,omitempty"`
}
func (g GeminiChatRequest) ByteJson() []byte {
bytejson, _ := json.Marshal(g)
return bytejson
}
type GeminiContent struct {
Role string `json:"role,omitempty"`
Parts []GeminiPart `json:"parts,omitempty"`
}
type GeminiPart struct {
Text string `json:"text,omitempty"`
// InlineData GeminiPartInlineData `json:"inlineData,omitempty"`
}
type GeminiPartInlineData struct {
MimeType string `json:"mimeType,omitempty"`
Data string `json:"data,omitempty"` // base64
}
type GeminiResponse struct {
Candidates []struct {
Content struct {
Parts []struct {
Text string `json:"text"`
} `json:"parts"`
Role string `json:"role"`
} `json:"content"`
FinishReason string `json:"finishReason"`
Index int `json:"index"`
SafetyRatings []struct {
Category string `json:"category"`
Probability string `json:"probability"`
} `json:"safetyRatings"`
} `json:"candidates"`
PromptFeedback struct {
SafetyRatings []struct {
Category string `json:"category"`
Probability string `json:"probability"`
} `json:"safetyRatings"`
} `json:"promptFeedback"`
Error struct {
Code int `json:"code"`
Message string `json:"message"`
Status string `json:"status"`
Details []struct {
Type string `json:"@type"`
FieldViolations []struct {
Field string `json:"field"`
Description string `json:"description"`
} `json:"fieldViolations"`
} `json:"details"`
} `json:"error"`
}
func ChatProxy(c *gin.Context, chatReq *openai.ChatCompletionRequest) {
usagelog := store.Tokens{Model: chatReq.Model}
token, _ := c.Get("localuser")
lu, err := store.GetUserByToken(token.(string))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{
"message": err.Error(),
},
})
return
}
usagelog.UserID = int(lu.ID)
var prompts []genai.Part
var prompt string
for _, msg := range chatReq.Messages {
var visioncontent []openai.VisionContent
if err := json.Unmarshal(msg.Content, &visioncontent); err != nil {
prompt += "<" + msg.Role + ">: " + string(msg.Content) + "\n"
prompts = append(prompts, genai.Text("<"+msg.Role+">: "+string(msg.Content)))
} else {
if len(visioncontent) > 0 {
for _, content := range visioncontent {
if content.Type == "text" {
prompt += "<" + msg.Role + ">: " + content.Text + "\n"
prompts = append(prompts, genai.Text("<"+msg.Role+">: "+content.Text))
} else if content.Type == "image_url" {
if strings.HasPrefix(content.ImageURL.URL, "http") {
fmt.Println("链接:", content.ImageURL.URL)
} else if strings.HasPrefix(content.ImageURL.URL, "data:image") {
fmt.Println("base64:", content.ImageURL.URL[:20])
if chatReq.Model != "gemini-pro-vision" {
chatReq.Model = "gemini-pro-vision"
}
var mime string
// openai 会以 data:image 开头,则去掉 data:image/png;base64, 和 data:image/jpeg;base64,
if strings.HasPrefix(content.ImageURL.URL, "data:image/png") {
mime = "image/png"
} else if strings.HasPrefix(content.ImageURL.URL, "data:image/jpeg") {
mime = "image/jpeg"
} else {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Unsupported image format"})
return
}
imageString := strings.Split(content.ImageURL.URL, ",")[1]
imageBytes, err := base64.StdEncoding.DecodeString(imageString)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
prompts = append(prompts, genai.Blob{MIMEType: mime, Data: imageBytes})
}
// todo image tokens
}
}
}
}
if len(chatReq.Tools) > 0 {
tooljson, _ := json.Marshal(chatReq.Tools)
prompt += "<tools>: " + string(tooljson) + "\n"
// for _, tool := range chatReq.Tools {
// }
}
}
usagelog.PromptCount = tokenizer.NumTokensFromStr(prompt, chatReq.Model)
onekey, err := store.SelectKeyCache("google")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
ctx := context.Background()
client, err := genai.NewClient(ctx, option.WithAPIKey(onekey.Key))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
defer client.Close()
model := client.GenerativeModel(chatReq.Model)
iter := model.GenerateContentStream(ctx, prompts...)
datachan := make(chan string)
// closechan := make(chan error)
var result string
go func() {
for {
resp, err := iter.Next()
if err == iterator.Done {
var chatResp openai.ChatCompletionStreamResponse
chatResp.Model = chatReq.Model
choice := openai.Choice{}
choice.FinishReason = "stop"
chatResp.Choices = append(chatResp.Choices, choice)
datachan <- "data: " + string(chatResp.ByteJson())
close(datachan)
break
}
if err != nil {
log.Println(err)
var errResp openai.ErrResponse
errResp.Error.Code = "500"
errResp.Error.Message = err.Error()
datachan <- string(errResp.ByteJson())
close(datachan)
break
}
var content string
if resp.Candidates != nil && len(resp.Candidates) > 0 && len(resp.Candidates[0].Content.Parts) > 0 {
if s, ok := resp.Candidates[0].Content.Parts[0].(genai.Text); ok {
content = string(s)
result += content
}
} else {
continue
}
var chatResp openai.ChatCompletionStreamResponse
chatResp.Model = chatReq.Model
choice := openai.Choice{}
choice.Delta.Role = resp.Candidates[0].Content.Role
choice.Delta.Content = content
chatResp.Choices = append(chatResp.Choices, choice)
chunk := "data: " + string(chatResp.ByteJson()) + "\n\n"
datachan <- chunk
}
}()
c.Writer.Header().Set("Content-Type", "text/event-stream")
c.Writer.Header().Set("Cache-Control", "no-cache")
c.Writer.Header().Set("Connection", "keep-alive")
c.Writer.Header().Set("Transfer-Encoding", "chunked")
c.Writer.Header().Set("X-Accel-Buffering", "no")
c.Stream(func(w io.Writer) bool {
if data, ok := <-datachan; ok {
if strings.HasPrefix(data, "data: ") {
c.Writer.WriteString(data)
// c.Writer.WriteString("\n\n")
} else {
c.Writer.WriteHeader(http.StatusBadGateway)
c.Writer.WriteString(data)
}
c.Writer.Flush()
return true
}
go func() {
}()
return false
})
}

View File

@@ -1,335 +0,0 @@
package openai
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"opencatd-open/pkg/tokenizer"
"opencatd-open/store"
"os"
"strings"
"github.com/gin-gonic/gin"
)
const (
AzureApiVersion = "2024-02-01"
OpenAI_Endpoint = "https://api.openai.com/v1/chat/completions"
)
var (
BaseURL string // "https://api.openai.com"
AIGateWay_Endpoint = "https://gateway.ai.cloudflare.com/v1/431ba10f11200d544922fbca177aaa7f/openai/openai/chat/completions"
)
func init() {
if os.Getenv("OpenAI_Endpoint") != "" {
BaseURL = os.Getenv("OpenAI_Endpoint")
}
if os.Getenv("AIGateWay_Endpoint") != "" {
AIGateWay_Endpoint = os.Getenv("AIGateWay_Endpoint")
}
}
// Vision Content
type VisionContent struct {
Type string `json:"type,omitempty"`
Text string `json:"text,omitempty"`
ImageURL *VisionImageURL `json:"image_url,omitempty"`
}
type VisionImageURL struct {
URL string `json:"url,omitempty"`
Detail string `json:"detail,omitempty"`
}
type ChatCompletionMessage struct {
Role string `json:"role"`
Content json.RawMessage `json:"content"`
Name string `json:"name,omitempty"`
}
type FunctionDefinition struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Parameters any `json:"parameters"`
}
type Tool struct {
Type string `json:"type"`
Function *FunctionDefinition `json:"function,omitempty"`
}
type ChatCompletionRequest struct {
Model string `json:"model"`
Messages []ChatCompletionMessage `json:"messages"`
MaxTokens int `json:"max_tokens,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"`
N int `json:"n,omitempty"`
Stream bool `json:"stream,omitempty"`
Stop []string `json:"stop,omitempty"`
PresencePenalty float64 `json:"presence_penalty,omitempty"`
FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
LogitBias map[string]int `json:"logit_bias,omitempty"`
User string `json:"user,omitempty"`
// Functions []FunctionDefinition `json:"functions,omitempty"`
// FunctionCall any `json:"function_call,omitempty"`
Tools []Tool `json:"tools,omitempty"`
// ToolChoice any `json:"tool_choice,omitempty"`
}
func (c ChatCompletionRequest) ToByteJson() []byte {
bytejson, _ := json.Marshal(c)
return bytejson
}
type ToolCall struct {
ID string `json:"id"`
Type string `json:"type"`
Function struct {
Name string `json:"name"`
Arguments string `json:"arguments"`
} `json:"function"`
}
type ChatCompletionResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int `json:"created"`
Model string `json:"model"`
Choices []struct {
Index int `json:"index"`
Message struct {
Role string `json:"role"`
Content string `json:"content"`
ToolCalls []ToolCall `json:"tool_calls"`
} `json:"message"`
Logprobs string `json:"logprobs"`
FinishReason string `json:"finish_reason"`
} `json:"choices"`
Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
} `json:"usage"`
SystemFingerprint string `json:"system_fingerprint"`
}
type Choice struct {
Index int `json:"index"`
Delta struct {
Role string `json:"role"`
Content string `json:"content"`
ToolCalls []ToolCall `json:"tool_calls"`
} `json:"delta"`
FinishReason string `json:"finish_reason"`
Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
} `json:"usage"`
}
type ChatCompletionStreamResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int `json:"created"`
Model string `json:"model"`
Choices []Choice `json:"choices"`
}
func (c *ChatCompletionStreamResponse) ByteJson() []byte {
bytejson, _ := json.Marshal(c)
return bytejson
}
func ChatProxy(c *gin.Context, chatReq *ChatCompletionRequest) {
usagelog := store.Tokens{Model: chatReq.Model}
token, _ := c.Get("localuser")
lu, err := store.GetUserByToken(token.(string))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{
"message": err.Error(),
},
})
return
}
usagelog.UserID = int(lu.ID)
var prompt string
for _, msg := range chatReq.Messages {
// prompt += "<" + msg.Role + ">: " + msg.Content + "\n"
var visioncontent []VisionContent
if err := json.Unmarshal(msg.Content, &visioncontent); err != nil {
prompt += "<" + msg.Role + ">: " + string(msg.Content) + "\n"
} else {
if len(visioncontent) > 0 {
for _, content := range visioncontent {
if content.Type == "text" {
prompt += "<" + msg.Role + ">: " + content.Text + "\n"
} else if content.Type == "image_url" {
if strings.HasPrefix(content.ImageURL.URL, "http") {
fmt.Println("链接:", content.ImageURL.URL)
} else if strings.HasPrefix(content.ImageURL.URL, "data:image") {
fmt.Println("base64:", content.ImageURL.URL[:20])
}
// todo image tokens
}
}
}
}
if len(chatReq.Tools) > 0 {
tooljson, _ := json.Marshal(chatReq.Tools)
prompt += "<tools>: " + string(tooljson) + "\n"
}
}
usagelog.PromptCount = tokenizer.NumTokensFromStr(prompt, chatReq.Model)
onekey, err := store.SelectKeyCache("openai")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var req *http.Request
switch onekey.ApiType {
case "azure":
var buildurl string
if onekey.EndPoint != "" {
buildurl = fmt.Sprintf("%s/openai/deployments/%s/chat/completions?api-version=%s", onekey.EndPoint, modelmap(chatReq.Model), AzureApiVersion)
} else {
buildurl = fmt.Sprintf("https://%s.openai.azure.com/openai/deployments/%s/chat/completions?api-version=%s", onekey.ResourceNmae, modelmap(chatReq.Model), AzureApiVersion)
}
req, err = http.NewRequest(c.Request.Method, buildurl, bytes.NewReader(chatReq.ToByteJson()))
req.Header = c.Request.Header
req.Header.Set("api-key", onekey.Key)
default:
if onekey.EndPoint != "" { // 优先key的endpoint
req, err = http.NewRequest(c.Request.Method, onekey.EndPoint+c.Request.RequestURI, bytes.NewReader(chatReq.ToByteJson()))
} else {
if BaseURL != "" { // 其次BaseURL
req, err = http.NewRequest(c.Request.Method, BaseURL+c.Request.RequestURI, bytes.NewReader(chatReq.ToByteJson()))
} else { // 最后是gateway的endpoint
req, err = http.NewRequest(c.Request.Method, AIGateWay_Endpoint, bytes.NewReader(chatReq.ToByteJson()))
}
}
req.Header = c.Request.Header
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", onekey.Key))
}
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
defer resp.Body.Close()
teeReader := io.TeeReader(resp.Body, c.Writer)
var result string
if chatReq.Stream {
// 流式响应
scanner := bufio.NewScanner(teeReader)
for scanner.Scan() {
line := scanner.Bytes()
if len(line) > 0 && bytes.HasPrefix(line, []byte("data: ")) {
if bytes.HasPrefix(line, []byte("data: [DONE]")) {
break
}
var opiResp ChatCompletionStreamResponse
line = bytes.Replace(line, []byte("data: "), []byte(""), -1)
line = bytes.TrimSpace(line)
if err := json.Unmarshal(line, &opiResp); err != nil {
continue
}
if opiResp.Choices != nil && len(opiResp.Choices) > 0 {
if opiResp.Choices[0].Delta.Role != "" {
result += "<" + opiResp.Choices[0].Delta.Role + "> "
}
result += opiResp.Choices[0].Delta.Content // 计算Content Token
if len(opiResp.Choices[0].Delta.ToolCalls) > 0 { // 计算ToolCalls token
if opiResp.Choices[0].Delta.ToolCalls[0].Function.Name != "" {
result += "name:" + opiResp.Choices[0].Delta.ToolCalls[0].Function.Name + " arguments:"
}
result += opiResp.Choices[0].Delta.ToolCalls[0].Function.Arguments
}
} else {
continue
}
}
}
} else {
// 处理非流式响应
body, err := io.ReadAll(teeReader)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var opiResp ChatCompletionResponse
if err := json.Unmarshal(body, &opiResp); err != nil {
log.Println("Error parsing JSON:", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error parsing JSON," + err.Error()})
return
}
if opiResp.Choices != nil && len(opiResp.Choices) > 0 {
if opiResp.Choices[0].Message.Role != "" {
result += "<" + opiResp.Choices[0].Message.Role + "> "
}
result += opiResp.Choices[0].Message.Content
if len(opiResp.Choices[0].Message.ToolCalls) > 0 {
if opiResp.Choices[0].Message.ToolCalls[0].Function.Name != "" {
result += "name:" + opiResp.Choices[0].Message.ToolCalls[0].Function.Name + " arguments:"
}
result += opiResp.Choices[0].Message.ToolCalls[0].Function.Arguments
}
}
}
usagelog.CompletionCount = tokenizer.NumTokensFromStr(result, chatReq.Model)
usagelog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(usagelog.Model, usagelog.PromptCount, usagelog.CompletionCount))
if err := store.Record(&usagelog); err != nil {
log.Println(err)
}
if err := store.SumDaily(usagelog.UserID); err != nil {
log.Println(err)
}
}
func modelmap(in string) string {
// gpt-3.5-turbo -> gpt-35-turbo
if strings.Contains(in, ".") {
return strings.ReplaceAll(in, ".", "")
}
return in
}
type ErrResponse struct {
Error struct {
Message string `json:"message"`
Code string `json:"code"`
} `json:"error"`
}
func (e *ErrResponse) ByteJson() []byte {
bytejson, _ := json.Marshal(e)
return bytejson
}

View File

@@ -1,149 +0,0 @@
package openai
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/http/httputil"
"net/url"
"opencatd-open/pkg/tokenizer"
"opencatd-open/store"
"strconv"
"github.com/duke-git/lancet/v2/slice"
"github.com/gin-gonic/gin"
)
const (
DalleEndpoint = "https://api.openai.com/v1/images/generations"
DalleEditEndpoint = "https://api.openai.com/v1/images/edits"
DalleVariationEndpoint = "https://api.openai.com/v1/images/variations"
)
type DallERequest struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
N int `form:"n" json:"n,omitempty"`
Size string `form:"size" json:"size,omitempty"`
Quality string `json:"quality,omitempty"` // standard,hd
Style string `json:"style,omitempty"` // vivid,natural
ResponseFormat string `json:"response_format,omitempty"` // url or b64_json
}
func DalleHandler(c *gin.Context) {
var dalleRequest DallERequest
if err := c.ShouldBind(&dalleRequest); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
if dalleRequest.N == 0 {
dalleRequest.N = 1
}
if dalleRequest.Size == "" {
dalleRequest.Size = "512x512"
}
model := dalleRequest.Model
var chatlog store.Tokens
chatlog.CompletionCount = dalleRequest.N
if model == "dall-e" {
model = "dall-e-2"
}
model = model + "." + dalleRequest.Size
if dalleRequest.Model == "dall-e-2" || dalleRequest.Model == "dall-e" {
if !slice.Contain([]string{"256x256", "512x512", "1024x1024"}, dalleRequest.Size) {
c.JSON(http.StatusBadRequest, gin.H{
"error": gin.H{
"message": fmt.Sprintf("Invalid size: %s for %s", dalleRequest.Size, dalleRequest.Model),
},
})
return
}
} else if dalleRequest.Model == "dall-e-3" {
if !slice.Contain([]string{"256x256", "512x512", "1024x1024", "1792x1024", "1024x1792"}, dalleRequest.Size) {
c.JSON(http.StatusBadRequest, gin.H{
"error": gin.H{
"message": fmt.Sprintf("Invalid size: %s for %s", dalleRequest.Size, dalleRequest.Model),
},
})
return
}
if dalleRequest.Quality == "hd" {
model = model + ".hd"
}
} else {
c.JSON(http.StatusBadRequest, gin.H{
"error": gin.H{
"message": fmt.Sprintf("Invalid model: %s", dalleRequest.Model),
},
})
return
}
chatlog.Model = model
token, _ := c.Get("localuser")
lu, err := store.GetUserByToken(token.(string))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{
"message": err.Error(),
},
})
return
}
chatlog.UserID = int(lu.ID)
key, err := store.SelectKeyCache("openai")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{
"message": err.Error(),
},
})
return
}
targetURL, _ := url.Parse(DalleEndpoint)
proxy := httputil.NewSingleHostReverseProxy(targetURL)
proxy.Director = func(req *http.Request) {
req.Header.Set("Authorization", "Bearer "+key.Key)
req.Header.Set("Content-Type", "application/json")
req.Host = targetURL.Host
req.URL.Scheme = targetURL.Scheme
req.URL.Host = targetURL.Host
req.URL.Path = targetURL.Path
req.URL.RawPath = targetURL.RawPath
req.URL.RawQuery = targetURL.RawQuery
bytebody, _ := json.Marshal(dalleRequest)
req.Body = io.NopCloser(bytes.NewBuffer(bytebody))
req.ContentLength = int64(len(bytebody))
req.Header.Set("Content-Length", strconv.Itoa(len(bytebody)))
}
proxy.ModifyResponse = func(resp *http.Response) error {
if resp.StatusCode == http.StatusOK {
chatlog.TotalTokens = chatlog.PromptCount + chatlog.CompletionCount
chatlog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
if err := store.Record(&chatlog); err != nil {
log.Println(err)
}
if err := store.SumDaily(chatlog.UserID); err != nil {
log.Println(err)
}
}
return nil
}
proxy.ServeHTTP(c.Writer, c.Request)
}

View File

@@ -1,93 +0,0 @@
package openai
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/http/httputil"
"net/url"
"opencatd-open/pkg/tokenizer"
"opencatd-open/store"
"github.com/gin-gonic/gin"
)
const (
SpeechEndpoint = "https://api.openai.com/v1/audio/speech"
)
type SpeechRequest struct {
Model string `json:"model"`
Input string `json:"input"`
Voice string `json:"voice"`
}
func SpeechHandler(c *gin.Context) {
var chatreq SpeechRequest
if err := c.ShouldBindJSON(&chatreq); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var chatlog store.Tokens
chatlog.Model = chatreq.Model
chatlog.CompletionCount = len(chatreq.Input)
token, _ := c.Get("localuser")
lu, err := store.GetUserByToken(token.(string))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{
"message": err.Error(),
},
})
return
}
chatlog.UserID = int(lu.ID)
key, err := store.SelectKeyCache("openai")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{
"message": err.Error(),
},
})
return
}
targetURL, _ := url.Parse(SpeechEndpoint)
proxy := httputil.NewSingleHostReverseProxy(targetURL)
proxy.Director = func(req *http.Request) {
req.Header = c.Request.Header
req.Header["Authorization"] = []string{"Bearer " + key.Key}
req.Host = targetURL.Host
req.URL.Scheme = targetURL.Scheme
req.URL.Host = targetURL.Host
req.URL.Path = targetURL.Path
req.URL.RawPath = targetURL.RawPath
reqBytes, _ := json.Marshal(chatreq)
req.Body = io.NopCloser(bytes.NewReader(reqBytes))
req.ContentLength = int64(len(reqBytes))
}
proxy.ModifyResponse = func(resp *http.Response) error {
if resp.StatusCode == http.StatusOK {
chatlog.TotalTokens = chatlog.PromptCount + chatlog.CompletionCount
chatlog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
if err := store.Record(&chatlog); err != nil {
log.Println(err)
}
if err := store.SumDaily(chatlog.UserID); err != nil {
log.Println(err)
}
}
return nil
}
proxy.ServeHTTP(c.Writer, c.Request)
}

View File

@@ -1,181 +0,0 @@
package tokenizer
import (
"fmt"
"log"
"strings"
"github.com/pkoukk/tiktoken-go"
"github.com/sashabaranov/go-openai"
)
func NumTokensFromMessages(messages []openai.ChatCompletionMessage, model string) (numTokens int) {
tkm, err := tiktoken.EncodingForModel(model)
if err != nil {
err = fmt.Errorf("EncodingForModel: %v", err)
log.Println(err)
return
}
var tokensPerMessage, tokensPerName int
switch model {
case "gpt-3.5-turbo",
"gpt-3.5-turbo-0613",
"gpt-3.5-turbo-16k",
"gpt-3.5-turbo-16k-0613",
"gpt-4",
"gpt-4-0314",
"gpt-4-0613",
"gpt-4-32k",
"gpt-4-32k-0314",
"gpt-4-32k-0613":
tokensPerMessage = 3
tokensPerName = 1
case "gpt-3.5-turbo-0301":
tokensPerMessage = 4 // every message follows <|start|>{role/name}\n{content}<|end|>\n
tokensPerName = -1 // if there's a name, the role is omitted
default:
if strings.Contains(model, "gpt-3.5-turbo") {
log.Println("warning: gpt-3.5-turbo may update over time. Returning num tokens assuming gpt-3.5-turbo-0613.")
return NumTokensFromMessages(messages, "gpt-3.5-turbo-0613")
} else if strings.Contains(model, "gpt-4") {
log.Println("warning: gpt-4 may update over time. Returning num tokens assuming gpt-4-0613.")
return NumTokensFromMessages(messages, "gpt-4-0613")
} else {
err = fmt.Errorf("warning: unknown model [%s]. Use default calculation method converted tokens.", model)
log.Println(err)
return NumTokensFromMessages(messages, "gpt-3.5-turbo-0613")
}
}
for _, message := range messages {
numTokens += tokensPerMessage
numTokens += len(tkm.Encode(message.Content, nil, nil))
numTokens += len(tkm.Encode(message.Role, nil, nil))
numTokens += len(tkm.Encode(message.Name, nil, nil))
if message.Name != "" {
numTokens += tokensPerName
}
}
numTokens += 3
return numTokens
}
func NumTokensFromStr(messages string, model string) (num_tokens int) {
tkm, err := tiktoken.EncodingForModel(model)
if err != nil {
fmt.Println(err)
fmt.Println("Unsupport Model,use cl100k_base Encode")
tkm, _ = tiktoken.GetEncoding("cl100k_base")
}
num_tokens += len(tkm.Encode(messages, nil, nil))
return num_tokens
}
// https://openai.com/pricing
func Cost(model string, promptCount, completionCount int) float64 {
var cost, prompt, completion float64
prompt = float64(promptCount)
completion = float64(completionCount)
switch model {
case "gpt-3.5-turbo-0301":
cost = 0.002 * float64((prompt+completion)/1000)
case "gpt-3.5-turbo", "gpt-3.5-turbo-0613", "gpt-3.5-turbo-1106", "gpt-3.5-turbo-0125":
cost = 0.0015*float64((prompt)/1000) + 0.002*float64(completion/1000)
case "gpt-3.5-turbo-16k", "gpt-3.5-turbo-16k-0613":
cost = 0.003*float64((prompt)/1000) + 0.004*float64(completion/1000)
case "gpt-4", "gpt-4-0613", "gpt-4-0314":
cost = 0.03*float64(prompt/1000) + 0.06*float64(completion/1000)
case "gpt-4-32k", "gpt-4-32k-0314", "gpt-4-32k-0613":
cost = 0.06*float64(prompt/1000) + 0.12*float64(completion/1000)
case "gpt-4-1106-preview", "gpt-4-vision-preview", "gpt-4-0125-preview", "gpt-4-turbo-preview":
cost = 0.01*float64(prompt/1000) + 0.03*float64(completion/1000)
case "gpt-4-turbo", "gpt-4-turbo-2024-04-09":
cost = 0.01*float64(prompt/1000) + 0.03*float64(completion/1000)
case "gpt-4o", "gpt-4o-2024-05-13":
cost = 0.005*float64(prompt/1000) + 0.015*float64(completion/1000)
case "whisper-1":
// 0.006$/min
cost = 0.006 * float64(prompt+completion) / 60
case "tts-1":
cost = 0.015 * float64(prompt+completion)
case "tts-1-hd":
cost = 0.03 * float64(prompt+completion)
case "dall-e-2.256x256":
cost = float64(0.016 * completion)
case "dall-e-2.512x512":
cost = float64(0.018 * completion)
case "dall-e-2.1024x1024":
cost = float64(0.02 * completion)
case "dall-e-3.256x256":
cost = float64(0.04 * completion)
case "dall-e-3.512x512":
cost = float64(0.04 * completion)
case "dall-e-3.1024x1024":
cost = float64(0.04 * completion)
case "dall-e-3.1024x1792", "dall-e-3.1792x1024":
cost = float64(0.08 * completion)
case "dall-e-3.256x256.hd":
cost = float64(0.08 * completion)
case "dall-e-3.512x512.hd":
cost = float64(0.08 * completion)
case "dall-e-3.1024x1024.hd":
cost = float64(0.08 * completion)
case "dall-e-3.1024x1792.hd", "dall-e-3.1792x1024.hd":
cost = float64(0.12 * completion)
// claude /million tokens
// https://aws.amazon.com/cn/bedrock/pricing/
case "claude-v1", "claude-v1-100k":
cost = 11.02/1000000*float64(prompt) + (32.68/1000000)*float64(completion)
case "claude-instant-v1", "claude-instant-v1-100k":
cost = (1.63/1000000)*float64(prompt) + (5.51/1000000)*float64(completion)
case "claude-2", "claude-2.1":
cost = (8.0/1000000)*float64(prompt) + (24.0/1000000)*float64(completion)
case "claude-3-haiku":
cost = (0.00025/1000)*float64(prompt) + (0.00125/1000)*float64(completion)
case "claude-3-sonnet":
cost = (0.003/1000)*float64(prompt) + (0.015/1000)*float64(completion)
case "claude-3-opus":
cost = (0.015/1000)*float64(prompt) + (0.075/1000)*float64(completion)
case "claude-3-haiku-20240307":
cost = (0.00025/1000)*float64(prompt) + (0.00125/1000)*float64(completion)
case "claude-3-sonnet-20240229":
cost = (0.003/1000)*float64(prompt) + (0.015/1000)*float64(completion)
case "claude-3-opus-20240229":
cost = (0.015/1000)*float64(prompt) + (0.075/1000)*float64(completion)
// google
// https://ai.google.dev/pricing?hl=zh-cn
case "gemini-pro":
cost = (0.0005/1000)*float64(prompt) + (0.0015/1000)*float64(completion)
case "gemini-pro-vision":
cost = (0.0005/1000)*float64(prompt) + (0.0015/1000)*float64(completion)
case "gemini-1.5-pro-latest":
cost = (0.0035/1000)*float64(prompt) + (0.0105/1000)*float64(completion)
case "gemini-1.5-flash-latest":
cost = (0.00035/1000)*float64(prompt) + (0.00053/1000)*float64(completion)
// Mistral AI
// https://docs.mistral.ai/platform/pricing/
case "mistral-small-latest":
cost = (0.002/1000)*float64(prompt) + (0.006/1000)*float64(completion)
case "mistral-medium-latest":
cost = (0.0027/1000)*float64(prompt) + (0.0081/1000)*float64(completion)
case "mistral-large-latest":
cost = (0.008/1000)*float64(prompt) + (0.024/1000)*float64(completion)
default:
if strings.Contains(model, "gpt-3.5-turbo") {
cost = 0.003 * float64((prompt+completion)/1000)
} else if strings.Contains(model, "gpt-4") {
cost = 0.06 * float64((prompt+completion)/1000)
} else {
cost = 0.002 * float64((prompt+completion)/1000)
}
}
return cost
}

View File

@@ -1,35 +0,0 @@
package router
import (
"net/http"
"strings"
"opencatd-open/pkg/claude"
"opencatd-open/pkg/google"
"opencatd-open/pkg/openai"
"github.com/gin-gonic/gin"
)
func ChatHandler(c *gin.Context) {
var chatreq openai.ChatCompletionRequest
if err := c.ShouldBindJSON(&chatreq); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if strings.HasPrefix(chatreq.Model, "gpt") {
openai.ChatProxy(c, &chatreq)
return
}
if strings.HasPrefix(chatreq.Model, "claude") {
claude.ChatProxy(c, &chatreq)
return
}
if strings.HasPrefix(chatreq.Model, "gemini") {
google.ChatProxy(c, &chatreq)
return
}
}

View File

@@ -3,37 +3,27 @@ package router
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"mime/multipart"
"net"
"net/http"
"net/http/httputil"
"net/url"
"opencatd-open/pkg/azureopenai"
"opencatd-open/pkg/claude"
oai "opencatd-open/pkg/openai"
"opencatd-open/pkg/tokenizer"
"opencatd-open/store"
"os"
"path/filepath"
"strings"
"time"
"github.com/Sakurasan/to"
"github.com/duke-git/lancet/v2/cryptor"
"github.com/faiface/beep"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/wav"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/pkoukk/tiktoken-go"
"github.com/sashabaranov/go-openai"
"gopkg.in/vansante/go-ffprobe.v2"
"gorm.io/gorm"
)
@@ -128,12 +118,6 @@ func AuthMiddleware() gin.HandlerFunc {
c.Abort()
return
}
if store.IsExistAuthCache(token[7:]) {
if strings.HasPrefix(c.Request.URL.Path, "/1/me") {
c.Next()
return
}
}
if token[7:] != rootToken {
u, err := store.GetUserByID(uint(1))
if err != nil {
@@ -194,8 +178,7 @@ func Handleinit(c *gin.Context) {
}
func HandleMe(c *gin.Context) {
token := c.GetHeader("Authorization")
u, err := store.GetUserByToken(token[7:])
u, err := store.GetUserByID(1)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
@@ -213,40 +196,6 @@ func HandleMe(c *gin.Context) {
c.JSON(http.StatusOK, resJSON)
}
func HandleMeUsage(c *gin.Context) {
token := c.GetHeader("Authorization")
fromStr := c.Query("from")
toStr := c.Query("to")
getMonthStartAndEnd := func() (start, end string) {
loc, _ := time.LoadLocation("Local")
now := time.Now().In(loc)
year, month, _ := now.Date()
startOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, loc)
endOfMonth := startOfMonth.AddDate(0, 1, 0)
start = startOfMonth.Format("2006-01-02")
end = endOfMonth.Format("2006-01-02")
return
}
if fromStr == "" || toStr == "" {
fromStr, toStr = getMonthStartAndEnd()
}
user, err := store.GetUserByToken(token)
if err != nil {
c.AbortWithError(http.StatusForbidden, err)
return
}
usage, err := store.QueryUserUsage(to.String(user.ID), fromStr, toStr)
if err != nil {
c.AbortWithError(http.StatusForbidden, err)
return
}
c.JSON(200, usage)
}
func HandleKeys(c *gin.Context) {
keys, err := store.GetAllKeys()
if err != nil {
@@ -288,55 +237,7 @@ func HandleAddKey(c *gin.Context) {
return
}
k := &store.Key{
ApiType: "azure",
Name: body.Name,
Key: body.Key,
ResourceNmae: keynames[1],
EndPoint: body.Endpoint,
}
if err := store.CreateKey(k); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
"message": err.Error(),
}})
return
}
} else if strings.HasPrefix(body.Name, "claude.") {
keynames := strings.Split(body.Name, ".")
if len(keynames) < 2 {
c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{
"message": "Invalid Key Name",
}})
return
}
if body.Endpoint == "" {
body.Endpoint = "https://api.anthropic.com"
}
k := &store.Key{
// ApiType: "anthropic",
ApiType: "claude",
Name: body.Name,
Key: body.Key,
ResourceNmae: keynames[1],
EndPoint: body.Endpoint,
}
if err := store.CreateKey(k); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
"message": err.Error(),
}})
return
}
} else if strings.HasPrefix(body.Name, "google.") {
keynames := strings.Split(body.Name, ".")
if len(keynames) < 2 {
c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{
"message": "Invalid Key Name",
}})
return
}
k := &store.Key{
// ApiType: "anthropic",
ApiType: "google",
ApiType: "azure_openai",
Name: body.Name,
Key: body.Key,
ResourceNmae: keynames[1],
@@ -436,12 +337,8 @@ func HandleDelUser(c *gin.Context) {
func HandleResetUserToken(c *gin.Context) {
id := to.Int(c.Param("id"))
newtoken := c.Query("token")
if newtoken == "" {
newtoken = uuid.NewString()
}
if err := store.UpdateUser(uint(id), newtoken); err != nil {
if err := store.UpdateUser(uint(id), uuid.NewString()); err != nil {
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
return
}
@@ -485,7 +382,6 @@ func HandleProy(c *gin.Context) {
chatreq = openai.ChatCompletionRequest{}
chatres = openai.ChatCompletionResponse{}
chatlog store.Tokens
onekey store.Key
pre_prompt string
req *http.Request
err error
@@ -494,24 +390,6 @@ func HandleProy(c *gin.Context) {
auth := c.Request.Header.Get("Authorization")
if len(auth) > 7 && auth[:7] == "Bearer " {
localuser = store.IsExistAuthCache(auth[7:])
c.Set("localuser", auth[7:])
}
if c.Request.URL.Path == "/v1/complete" {
claude.ClaudeProxy(c)
return
}
if c.Request.URL.Path == "/v1/audio/transcriptions" {
WhisperProxy(c)
return
}
if c.Request.URL.Path == "/v1/audio/speech" {
oai.SpeechHandler(c)
return
}
if c.Request.URL.Path == "/v1/images/generations" {
oai.DalleHandler(c)
return
}
if c.Request.URL.Path == "/v1/chat/completions" && localuser {
@@ -521,53 +399,30 @@ func HandleProy(c *gin.Context) {
}})
return
}
ChatHandler(c)
return
onekey := store.FromKeyCacheRandomItemKey()
if err := c.BindJSON(&chatreq); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
chatlog.Model = chatreq.Model
for _, m := range chatreq.Messages {
pre_prompt += m.Content + "\n"
}
chatlog.PromptHash = cryptor.Md5String(pre_prompt)
chatlog.PromptCount = tokenizer.NumTokensFromMessages(chatreq.Messages, chatreq.Model)
chatlog.PromptCount = NumTokensFromMessages(chatreq.Messages, chatreq.Model)
isStream = chatreq.Stream
chatlog.UserID, _ = store.GetUserID(auth[7:])
var body bytes.Buffer
json.NewEncoder(&body).Encode(chatreq)
if strings.HasPrefix(chatreq.Model, "claude-") {
onekey, err = store.SelectKeyCache("claude")
if err != nil {
c.AbortWithError(http.StatusForbidden, err)
}
} else {
onekey = store.FromKeyCacheRandomItemKey()
}
// 创建 API 请求
switch onekey.ApiType {
case "claude":
payload, _ := claude.TransReq(&chatreq)
buildurl := "https://api.anthropic.com/v1/complete"
req, err = http.NewRequest("POST", buildurl, payload)
req.Header.Add("accept", "application/json")
req.Header.Add("anthropic-version", "2023-06-01")
req.Header.Add("x-api-key", onekey.Key)
req.Header.Add("content-type", "application/json")
case "azure":
fallthrough
case "azure_openai":
var buildurl string
var apiVersion = "2023-05-15"
if onekey.EndPoint != "" {
buildurl = fmt.Sprintf("%s/openai/deployments/%s/chat/completions?api-version=%s", onekey.EndPoint, modelmap(chatreq.Model), apiVersion)
buildurl = fmt.Sprintf("https://%s/openai/deployments/%s/chat/completions?api-version=%s", onekey.EndPoint, modelmap(chatreq.Model), apiVersion)
} else {
buildurl = fmt.Sprintf("https://%s.openai.azure.com/openai/deployments/%s/chat/completions?api-version=%s", onekey.ResourceNmae, modelmap(chatreq.Model), apiVersion)
}
@@ -577,12 +432,7 @@ func HandleProy(c *gin.Context) {
case "openai":
fallthrough
default:
if onekey.EndPoint != "" {
req, err = http.NewRequest(c.Request.Method, onekey.EndPoint+c.Request.RequestURI, &body)
} else {
req, err = http.NewRequest(c.Request.Method, baseUrl+c.Request.RequestURI, &body)
}
req, err = http.NewRequest(c.Request.Method, baseUrl+c.Request.RequestURI, &body)
req.Header = c.Request.Header
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", onekey.Key))
}
@@ -596,7 +446,7 @@ func HandleProy(c *gin.Context) {
req, err = http.NewRequest(c.Request.Method, baseUrl+c.Request.RequestURI, c.Request.Body)
if err != nil {
log.Println(err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
return
}
req.Header = c.Request.Header
@@ -605,7 +455,7 @@ func HandleProy(c *gin.Context) {
resp, err := client.Do(req)
if err != nil {
log.Println(err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
return
}
defer resp.Body.Close()
@@ -637,49 +487,41 @@ func HandleProy(c *gin.Context) {
reader := bufio.NewReader(resp.Body)
if resp.StatusCode == 200 && localuser {
switch onekey.ApiType {
case "claude":
claude.TransRsp(c, isStream, chatlog, reader)
return
case "openai", "azure", "azure_openai":
fallthrough
default:
if isStream {
contentCh := fetchResponseContent(c, reader)
var buffer bytes.Buffer
for content := range contentCh {
buffer.WriteString(content)
}
chatlog.CompletionCount = tokenizer.NumTokensFromStr(buffer.String(), chatreq.Model)
chatlog.TotalTokens = chatlog.PromptCount + chatlog.CompletionCount
chatlog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
if err := store.Record(&chatlog); err != nil {
log.Println(err)
}
if err := store.SumDaily(chatlog.UserID); err != nil {
log.Println(err)
}
return
if isStream {
contentCh := fetchResponseContent(c, reader)
var buffer bytes.Buffer
for content := range contentCh {
buffer.WriteString(content)
}
res, err := io.ReadAll(reader)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": gin.H{
"message": err.Error(),
}})
return
}
reader = bufio.NewReader(bytes.NewBuffer(res))
json.NewDecoder(bytes.NewBuffer(res)).Decode(&chatres)
chatlog.PromptCount = chatres.Usage.PromptTokens
chatlog.CompletionCount = chatres.Usage.CompletionTokens
chatlog.TotalTokens = chatres.Usage.TotalTokens
chatlog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
chatlog.CompletionCount = NumTokensFromStr(buffer.String(), chatreq.Model)
chatlog.TotalTokens = chatlog.PromptCount + chatlog.CompletionCount
chatlog.Cost = fmt.Sprintf("%.6f", Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
if err := store.Record(&chatlog); err != nil {
log.Println(err)
}
if err := store.SumDaily(chatlog.UserID); err != nil {
log.Println(err)
}
return
}
res, err := io.ReadAll(reader)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": gin.H{
"message": err.Error(),
}})
return
}
reader = bufio.NewReader(bytes.NewBuffer(res))
json.NewDecoder(bytes.NewBuffer(res)).Decode(&chatres)
chatlog.PromptCount = chatres.Usage.PromptTokens
chatlog.CompletionCount = chatres.Usage.CompletionTokens
chatlog.TotalTokens = chatres.Usage.TotalTokens
chatlog.Cost = fmt.Sprintf("%.6f", Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
if err := store.Record(&chatlog); err != nil {
log.Println(err)
}
if err := store.SumDaily(chatlog.UserID); err != nil {
log.Println(err)
}
}
@@ -740,26 +582,25 @@ func HandleReverseProxy(c *gin.Context) {
proxy.ServeHTTP(c.Writer, req)
}
func Cost(model string, promptCount, completionCount int) float64 {
var cost, prompt, completion float64
prompt = float64(promptCount)
completion = float64(completionCount)
switch model {
case "gpt-3.5-turbo", "gpt-3.5-turbo-0301":
cost = 0.002 * float64((prompt+completion)/1000)
case "gpt-4", "gpt-4-0314":
cost = 0.03*float64(prompt/1000) + 0.06*float64(completion/1000)
case "gpt-4-32k", "gpt-4-32k-0314":
cost = 0.06*float64(prompt/1000) + 0.12*float64(completion/1000)
}
return cost
}
func HandleUsage(c *gin.Context) {
fromStr := c.Query("from")
toStr := c.Query("to")
getMonthStartAndEnd := func() (start, end string) {
loc, _ := time.LoadLocation("Local")
now := time.Now().In(loc)
year, month, _ := now.Date()
startOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, loc)
endOfMonth := startOfMonth.AddDate(0, 1, 0)
start = startOfMonth.Format("2006-01-02")
end = endOfMonth.Format("2006-01-02")
return
}
if fromStr == "" || toStr == "" {
fromStr, toStr = getMonthStartAndEnd()
}
usage, err := store.QueryUsage(fromStr, toStr)
if err != nil {
@@ -818,163 +659,58 @@ func fetchResponseContent(ctx *gin.Context, responseBody *bufio.Reader) <-chan s
return contentCh
}
func NumTokensFromMessages(messages []openai.ChatCompletionMessage, model string) (num_tokens int) {
tkm, err := tiktoken.EncodingForModel(model)
if err != nil {
err = fmt.Errorf("EncodingForModel: %v", err)
fmt.Println(err)
return
}
var tokens_per_message int
var tokens_per_name int
if model == "gpt-3.5-turbo-0301" || model == "gpt-3.5-turbo" {
tokens_per_message = 4
tokens_per_name = -1
} else if model == "gpt-4-0314" || model == "gpt-4" {
tokens_per_message = 3
tokens_per_name = 1
} else {
fmt.Println("Warning: model not found. Using cl100k_base encoding.")
tokens_per_message = 3
tokens_per_name = 1
}
for _, message := range messages {
num_tokens += tokens_per_message
num_tokens += len(tkm.Encode(message.Content, nil, nil))
// num_tokens += len(tkm.Encode(message.Role, nil, nil))
if message.Name != "" {
num_tokens += tokens_per_name
}
}
num_tokens += 3
return num_tokens
}
func NumTokensFromStr(messages string, model string) (num_tokens int) {
tkm, err := tiktoken.EncodingForModel(model)
if err != nil {
err = fmt.Errorf("EncodingForModel: %v", err)
fmt.Println(err)
return
}
num_tokens += len(tkm.Encode(messages, nil, nil))
return num_tokens
}
func modelmap(in string) string {
// gpt-3.5-turbo -> gpt-35-turbo
if strings.Contains(in, ".") {
return strings.ReplaceAll(in, ".", "")
switch in {
case "gpt-3.5-turbo":
return "gpt-35-turbo"
case "gpt-4":
return "gpt-4"
}
return in
}
func WhisperProxy(c *gin.Context) {
var chatlog store.Tokens
byteBody, _ := io.ReadAll(c.Request.Body)
c.Request.Body = io.NopCloser(bytes.NewBuffer(byteBody))
model, _ := c.GetPostForm("model")
key, err := store.SelectKeyCache("openai")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{
"message": err.Error(),
},
})
return
}
chatlog.Model = model
token, _ := c.Get("localuser")
lu, err := store.GetUserByToken(token.(string))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{
"message": err.Error(),
},
})
return
}
chatlog.UserID = int(lu.ID)
if err := ParseWhisperRequestTokens(c, &chatlog, byteBody); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{
"message": err.Error(),
},
})
return
}
if key.EndPoint == "" {
key.EndPoint = "https://api.openai.com"
}
targetUrl, _ := url.ParseRequestURI(key.EndPoint + c.Request.URL.String())
log.Println(targetUrl)
proxy := httputil.NewSingleHostReverseProxy(targetUrl)
proxy.Director = func(req *http.Request) {
req.Host = targetUrl.Host
req.URL.Scheme = targetUrl.Scheme
req.URL.Host = targetUrl.Host
req.Header.Set("Authorization", "Bearer "+key.Key)
}
proxy.ModifyResponse = func(resp *http.Response) error {
if resp.StatusCode != http.StatusOK {
return nil
}
chatlog.TotalTokens = chatlog.PromptCount + chatlog.CompletionCount
chatlog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
if err := store.Record(&chatlog); err != nil {
log.Println(err)
}
if err := store.SumDaily(chatlog.UserID); err != nil {
log.Println(err)
}
return nil
}
proxy.ServeHTTP(c.Writer, c.Request)
}
func probe(fileReader io.Reader) (time.Duration, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelFn()
data, err := ffprobe.ProbeReader(ctx, fileReader)
if err != nil {
return 0, err
}
duration := data.Format.DurationSeconds
pduration, err := time.ParseDuration(fmt.Sprintf("%fs", duration))
if err != nil {
return 0, fmt.Errorf("Error parsing duration: %s", err)
}
return pduration, nil
}
func getAudioDuration(file *multipart.FileHeader) (time.Duration, error) {
var (
streamer beep.StreamSeekCloser
format beep.Format
err error
)
f, err := file.Open()
defer f.Close()
// Get the file extension to determine the audio file type
fileType := filepath.Ext(file.Filename)
switch fileType {
case ".mp3":
streamer, format, err = mp3.Decode(f)
case ".wav":
streamer, format, err = wav.Decode(f)
case ".m4a":
duration, err := probe(f)
if err != nil {
return 0, err
}
return duration, nil
default:
return 0, errors.New("unsupported audio file format")
}
if err != nil {
return 0, err
}
defer streamer.Close()
// Calculate the audio file's duration.
numSamples := streamer.Len()
sampleRate := format.SampleRate
duration := time.Duration(numSamples) * time.Second / time.Duration(sampleRate)
return duration, nil
}
func ParseWhisperRequestTokens(c *gin.Context, usage *store.Tokens, byteBody []byte) error {
file, _ := c.FormFile("file")
model, _ := c.GetPostForm("model")
usage.Model = model
if file != nil {
duration, err := getAudioDuration(file)
if err != nil {
return fmt.Errorf("Error getting audio duration:%s", err)
}
if duration > 5*time.Minute {
return fmt.Errorf("Audio duration exceeds 5 minutes")
}
// 计算时长,四舍五入到最接近的秒数
usage.PromptCount = int(duration.Round(time.Second).Seconds())
}
c.Request.Body = io.NopCloser(bytes.NewBuffer(byteBody))
return nil
}

View File

@@ -1,10 +1,8 @@
package store
import (
"errors"
"log"
"math/rand"
"time"
"github.com/Sakurasan/to"
"github.com/patrickmn/go-cache"
@@ -42,29 +40,6 @@ func FromKeyCacheRandomItemKey() Key {
return item.Object.(Key)
}
func SelectKeyCache(apitype string) (Key, error) {
var keys []Key
items := KeysCache.Items()
for _, item := range items {
if item.Object.(Key).ApiType == apitype {
keys = append(keys, item.Object.(Key))
}
if apitype == "openai" {
if item.Object.(Key).ApiType == "azure" {
keys = append(keys, item.Object.(Key))
}
}
}
if len(keys) == 0 {
return Key{}, errors.New("No key found")
} else if len(keys) == 1 {
return keys[0], nil
}
rand.Seed(time.Now().UnixNano())
idx := rand.Intn(len(keys))
return keys[idx], nil
}
func LoadAuthCache() {
AuthCache = cache.New(cache.NoExpiration, cache.NoExpiration)
users, err := GetAllUsers()

View File

@@ -68,21 +68,6 @@ func QueryUsage(from, to string) ([]CalcUsage, error) {
return results, nil
}
func QueryUserUsage(userid, from, to string) (*CalcUsage, error) {
var results = new(CalcUsage)
err := usage.Model(&DailyUsage{}).Select(`user_id,
--SUM(prompt_units) AS prompt_units,
-- SUM(completion_units) AS completion_units,
SUM(total_unit) AS total_unit,
printf('%.6f', SUM(cost)) AS cost`).
Where("user_id = ? AND date >= ? AND date < ?", userid, from, to).
Find(&results).Error
if err != nil {
return nil, err
}
return results, nil
}
type Tokens struct {
UserID int
PromptCount int

View File

@@ -2,14 +2,11 @@ package store
import (
"time"
"gorm.io/gorm"
)
type User struct {
gorm.Model
IsDelete bool `gorm:"default:false" json:"IsDelete"`
ID uint `gorm:"primarykey autoIncrement;" json:"id,omitempty"`
ID uint `gorm:"primaryKey;autoIncrement" json:"id,omitempty"`
Name string `gorm:"unique;not null" json:"name,omitempty"`
Token string `gorm:"unique;not null" json:"token,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
@@ -76,15 +73,6 @@ func GetUserByName(name string) (*User, error) {
return &user, nil
}
func GetUserByToken(token string) (*User, error) {
var user User
result := db.Where("token = ?", token).First(&user)
if result.Error != nil {
return nil, result.Error
}
return &user, nil
}
func GetUserID(authkey string) (int, error) {
var user User
result := db.Where(&User{Token: authkey}).First(&user)

19
web/package-lock.json generated
View File

@@ -8,7 +8,8 @@
"name": "web",
"version": "0.0.0",
"dependencies": {
"vue": "^3.2.47"
"vue": "^3.2.47",
"vue-router": "^4.2.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.1.0",
@@ -537,6 +538,11 @@
"@vue/shared": "3.3.2"
}
},
"node_modules/@vue/devtools-api": {
"version": "6.5.0",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz",
"integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
},
"node_modules/@vue/reactivity": {
"version": "3.3.2",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.3.2.tgz",
@@ -1604,6 +1610,17 @@
"@vue/shared": "3.3.2"
}
},
"node_modules/vue-router": {
"version": "4.2.2",
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.2.2.tgz",
"integrity": "sha512-cChBPPmAflgBGmy3tBsjeoe3f3VOSG6naKyY5pjtrqLGbNEXdzCigFUHgBvp9e3ysAtFtEx7OLqcSDh/1Cq2TQ==",
"dependencies": {
"@vue/devtools-api": "^6.5.0"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",

View File

@@ -9,7 +9,8 @@
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.2.47"
"vue": "^3.2.47",
"vue-router": "^4.2.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.1.0",

View File

@@ -1,161 +1,11 @@
<template>
<div class="flex flex-col h-screen w-screen bg-diy">
<header class="bg-zinc-700 text-white py-4 fixed top-0 w-full z-10">
<div class="container flex justify-between items-center px-4">
<div class="flex items-center">
<img class="h-8" src="./assets/logo.svg" alt="Logo">
<h1 class="ml-2 text-l">opencatd-open</h1>
</div>
<div class="flex justify-between items-center space-x-2">
<a href="https://github.com/mirrors2/opencatd-open">
<img class="h-6" alt="GitHub Repo stars"
src="https://img.shields.io/github/stars/mirrors2/opencatd-open?style=social">
</a>
<!-- <button class="bg-white text-gray-900 font-medium py-2 px-4 rounded-full hover:bg-gray-200 hover:text-gray-900">Log In</button> -->
</div>
</div>
</header>
<main class="w-screen flex-grow flex flex-col justify-center items-center bg-zinc-700 mt-16">
<div class="">
<div class=" flex items-center justify-center my-0">
<img src="./assets/logo.svg" alt="Project Logo" class="logo h-10 my-0">
</div>
<div class=" flex items-center justify-center my-0">
<a class="text-gray-300 text-4xl" href="https://github.com/mirrors2/opencatd-open">opencatd-open</a>
</div>
<div class="my-12">
<p class="text-gray-300 mt-2 mx-5">opencatd-open is an open-source, team-shared service for ChatGPT API that can
be safely shared with others for API usage.</p>
</div>
</div>
<div class="mt-8 shadow-lg mb-8 flax justify-center items-center">
<div class="flex gap-2">
<div>
<input class="w-60 py-2 px-2 bg-zinc-700 rounded-lg border focus:outline-none text-white flex-1" disabled name=""
id="" v-model="url">
</div>
<br>
<div>
<input type="submit" value="复制" @click="copyUrl"
class="bg-white font-medium py-2 px-3 hover:bg-gray-200 hover:text-gray-900 rounded-lg h-10 text-gray-700">
</div>
</div>
</div>
<div class="bg-diy w-screen h-auto flex flex-col overflow-x-auto justify-center items-center">
<div>
<p class="text-gray-500 mt-5 text-sm">👉Api-Keys: <a
href=https://platform.openai.com/account/api-keys>https://platform.openai.com/account/api-keys</a></p>
</div>
<section
class="w-10/12 h-auto mt-5 my-4 shadow-lg mb-8 p-12 bg-white border-2 border-gray-200 rounded-xl hover:shadow-2xl">
<!-- card content -->
<div class="flex justify-center">
<h1 class="text-4xl my-2">使用说明</h1>
</div>
<hr class="my-5">
<div class="text-xl mt-5">
<h2>作为OpenAI API代理</h2>
</div>
<div class="my-4 gap">
<p>由于OpenAI API不能再国内访问使用"openai api key+自定义域名"可以无感访问</p>
<p>在自定义地址中填入当前地址</p> <img class=" sm:max-w-full md:max-w-sm h-auto" src="./assets/usersdomain.jpg" alt="">
</div>
</section>
</div>
<div class="bg-diy w-screen h-auto flex flex-grow overflow-x-auto justify-center">
<section
class="w-10/12 h-auto my-2 shadow-lg mb-8 p-12 bg-white border-2 border-gray-200 rounded-xl hover:shadow-2xl">
<!-- card content -->
<div class="text-xl">
<h2>团队共享API模式</h2>
</div>
<div class="my-4 gap">
<p>团队共享模式可以把openai api key分发给多人使用.使用openai api key作为内部访问密钥</p>
<p>系统生成api-key,使用"系统生成的api-key+自定义域名"可以无感访问</p>
<p></p>
<p>在自定义地址中填入当前地址<span class="text-rose-500">OpenCat</span>为例(目前体验最好):</p> <img
class=" sm:max-w-full md:max-w-sm h-auto" src="./assets/team.jpg" alt="">
<hr class="my-5">
<blockquote>
<p>注意:第三方应用需要支持自定义 OpenAI Key Host</p>
</blockquote>
</div>
</section>
</div>
</main>
<footer class="bg-diy py-6 w-screen flex flex-col justify-center items-center">
<div class="bg-diy w-10/12 h-auto flex overflow-x-auto justify-between mx-60">
<ul class="flex space-x-4">
<li><a href="https://github.com/mirrors2/opencatd-open#qa" class="text-gray-700 hover:text-gray-900">FAQ</a>
</li>
</ul>
<p class="text-gray-700">© {{ currentYear }} <a href="https://github/mirrors2/opencatd-open">Sakurasan</a>. All
Rights
Reserved.</p>
</div>
</footer>
</div>
<router-view></router-view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import HelloWorld from './components/HelloWorld.vue'
const currentYear = ref('');
let url = ref('')
const copyUrl = () => {
navigator.clipboard.writeText(url.value).then(() => {
alert('复制成功!')
}, () => {
alert('复制失败,请手动复制。')
})
}
const getcurrentYear = () => {
currentYear.value = new Date().getFullYear().toString()
}
onMounted(() => {
url.value = window.location.origin;
getcurrentYear();
})
</script>
<style scoped>
.bg-diy {
background-color: #f0f0f0;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 3em #45f5e3aa);
}
p {
margin-bottom: 4px;
}
blockquote {
padding: 0 1em;
border-left: 0.25em solid #838989aa;
}
</style>

View File

@@ -2,4 +2,19 @@ import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App).mount('#app')
import {createRouter,createWebHashHistory} from 'vue-router'
const Home = { template: '<div>Home</div>' }
let routes = [
{ path: '/', component: ()=>import('./views/Home.vue') },
{ path: '/dash', component: ()=>import('./views/DashBoard.vue') },
{ path: '/signin', component: ()=>import('./views/Signin.vue') },
]
const router = createRouter({
history: createWebHashHistory(),
routes, // `routes: routes` 的缩写
})
createApp(App).use(router).mount('#app')

390
web/src/views/DashBoard.vue Normal file
View File

@@ -0,0 +1,390 @@
<template>
<aside class="ml-[-100%] fixed z-10 top-0 pb-3 px-6 w-full flex flex-col justify-between h-screen border-r bg-white transition duration-300 md:w-4/12 lg:ml-0 lg:w-[25%] xl:w-[20%] 2xl:w-[15%]">
<div>
<div class="-mx-6 px-6 py-4 flex items-center">
<a href="#" title="home">
<img src="../assets/logo.svg" class="w-10" alt="tailus logo">
</a>
<h1 class="ml-5">opencatd-open</h1>
</div>
<div class="mt-8 text-center">
<img src="../assets/logo.svg" alt="" class="w-10 h-10 m-auto rounded-full object-cover lg:w-28 lg:h-28">
<h5 class="hidden mt-4 text-xl font-semibold text-gray-600 lg:block">root</h5>
<span class="hidden text-gray-400 lg:block">Admin</span>
</div>
<ul class="space-y-2 tracking-wide mt-8">
<li>
<a href="#" aria-label="dashboard" class="relative px-4 py-3 flex items-center space-x-4 rounded-xl text-white bg-gradient-to-r from-sky-600 to-cyan-400">
<svg class="-ml-1 h-6 w-6" viewBox="0 0 24 24" fill="none">
<path d="M6 8a2 2 0 0 1 2-2h1a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V8ZM6 15a2 2 0 0 1 2-2h1a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2v-1Z" class="fill-current text-cyan-400 dark:fill-slate-600"></path>
<path d="M13 8a2 2 0 0 1 2-2h1a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2V8Z" class="fill-current text-cyan-200 group-hover:text-cyan-300"></path>
<path d="M13 15a2 2 0 0 1 2-2h1a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-1Z" class="fill-current group-hover:text-sky-300"></path>
</svg>
<span class="-mr-1 font-medium">Dashboard</span>
</a>
</li>
<li>
<a href="#" class="px-4 py-3 flex items-center space-x-4 rounded-md text-gray-600 group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path class="fill-current text-gray-300 group-hover:text-cyan-300" fill-rule="evenodd" d="M2 6a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1H8a3 3 0 00-3 3v1.5a1.5 1.5 0 01-3 0V6z" clip-rule="evenodd" />
<path class="fill-current text-gray-600 group-hover:text-cyan-600" d="M6 12a2 2 0 012-2h8a2 2 0 012 2v2a2 2 0 01-2 2H2h2a2 2 0 002-2v-2z" />
</svg>
<span class="group-hover:text-gray-700">Users</span>
</a>
</li>
<li>
<a href="#" class="px-4 py-3 flex items-center space-x-4 rounded-md text-gray-600 group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path class="fill-current text-gray-600 group-hover:text-cyan-600" fill-rule="evenodd" d="M2 5a2 2 0 012-2h8a2 2 0 012 2v10a2 2 0 002 2H4a2 2 0 01-2-2V5zm3 1h6v4H5V6zm6 6H5v2h6v-2z" clip-rule="evenodd" />
<path class="fill-current text-gray-300 group-hover:text-cyan-300" d="M15 7h1a2 2 0 012 2v5.5a1.5 1.5 0 01-3 0V7z" />
</svg>
<span class="group-hover:text-gray-700">Keys</span>
</a>
</li>
<li>
<a href="#" class="px-4 py-3 flex items-center space-x-4 rounded-md text-gray-600 group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path class="fill-current text-gray-600 group-hover:text-cyan-600" d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z" />
<path class="fill-current text-gray-300 group-hover:text-cyan-300" d="M12 2.252A8.014 8.014 0 0117.748 8H12V2.252z" />
</svg>
<span class="group-hover:text-gray-700">Usages</span>
</a>
</li>
<li>
<a href="#" class="px-4 py-3 flex items-center space-x-4 rounded-md text-gray-600 group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path class="fill-current text-gray-300 group-hover:text-cyan-300" d="M4 4a2 2 0 00-2 2v1h16V6a2 2 0 00-2-2H4z" />
<path class="fill-current text-gray-600 group-hover:text-cyan-600" fill-rule="evenodd" d="M18 9H2v5a2 2 0 002 2h12a2 2 0 002-2V9zM4 13a1 1 0 011-1h1a1 1 0 110 2H5a1 1 0 01-1-1zm5-1a1 1 0 100 2h1a1 1 0 100-2H9z" clip-rule="evenodd" />
</svg>
<span class="group-hover:text-gray-700">Recharge</span>
</a>
</li>
</ul>
</div>
<div class="px-6 -mx-6 pt-4 flex justify-between items-center border-t">
<button class="px-4 py-3 flex items-center space-x-4 rounded-md text-gray-600 group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
<span class="group-hover:text-gray-700">Logout</span>
</button>
</div>
</aside>
<div class="ml-auto mb-6 lg:w-[75%] xl:w-[80%] 2xl:w-[85%]">
<div class="sticky z-10 top-0 h-16 border-b bg-white lg:py-2.5">
<div class="px-6 flex items-center justify-between space-x-4 2xl:container">
<h5 hidden class="text-2xl text-gray-600 font-medium lg:block">Dashboard</h5>
<button class="w-12 h-16 -mr-2 border-r lg:hidden">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 my-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
<!-- <div class="flex space-x-4">
<div hidden class="md:block">
<div class="relative flex items-center text-gray-400 focus-within:text-cyan-400">
<span class="absolute left-4 h-6 flex items-center pr-3 border-r border-gray-300">
<svg xmlns="http://ww50w3.org/2000/svg" class="w-4 fill-current" viewBox="0 0 35.997 36.004">
<path id="Icon_awesome-search" data-name="search" d="M35.508,31.127l-7.01-7.01a1.686,1.686,0,0,0-1.2-.492H26.156a14.618,14.618,0,1,0-2.531,2.531V27.3a1.686,1.686,0,0,0,.492,1.2l7.01,7.01a1.681,1.681,0,0,0,2.384,0l1.99-1.99a1.7,1.7,0,0,0,.007-2.391Zm-20.883-7.5a9,9,0,1,1,9-9A8.995,8.995,0,0,1,14.625,23.625Z"></path>
</svg>
</span>
<input type="search" name="leadingIcon" id="leadingIcon" placeholder="Search here" class="w-full pl-14 pr-4 py-2.5 rounded-xl text-sm text-gray-600 outline-none border border-gray-300 focus:border-cyan-300 transition">
</div>
</div>
<button aria-label="search" class="w-10 h-10 rounded-xl border bg-gray-100 focus:bg-gray-100 active:bg-gray-200 md:hidden">
<svg xmlns="http://ww50w3.org/2000/svg" class="w-4 mx-auto fill-current text-gray-600" viewBox="0 0 35.997 36.004">
<path id="Icon_awesome-search" data-name="search" d="M35.508,31.127l-7.01-7.01a1.686,1.686,0,0,0-1.2-.492H26.156a14.618,14.618,0,1,0-2.531,2.531V27.3a1.686,1.686,0,0,0,.492,1.2l7.01,7.01a1.681,1.681,0,0,0,2.384,0l1.99-1.99a1.7,1.7,0,0,0,.007-2.391Zm-20.883-7.5a9,9,0,1,1,9-9A8.995,8.995,0,0,1,14.625,23.625Z"></path>
</svg>
</button>
<button aria-label="chat" class="w-10 h-10 rounded-xl border bg-gray-100 focus:bg-gray-100 active:bg-gray-200">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 m-auto text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" />
</svg>
</button>
<button aria-label="notification" class="w-10 h-10 rounded-xl border bg-gray-100 focus:bg-gray-100 active:bg-gray-200">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 m-auto text-gray-600" viewBox="0 0 20 20" fill="currentColor">
<path d="M10 2a6 6 0 00-6 6v3.586l-.707.707A1 1 0 004 14h12a1 1 0 00.707-1.707L16 11.586V8a6 6 0 00-6-6zM10 18a3 3 0 01-3-3h6a3 3 0 01-3 3z" />
</svg>
</button>
</div> -->
</div>
</div>
<div class="px-6 pt-6 2xl:container">
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<div class="md:col-span-2 lg:col-span-1" >
<div class="h-full py-8 px-6 space-y-6 rounded-xl border border-gray-200 bg-white">
<svg class="w-40 m-auto opacity-75" viewBox="0 0 146 146" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M73.1866 5.7129C81.999 5.7129 90.725 7.44863 98.8666 10.821C107.008 14.1933 114.406 19.1363 120.637 25.3675C126.868 31.5988 131.811 38.9964 135.184 47.138C138.556 55.2796 140.292 64.0057 140.292 72.818C140.292 81.6304 138.556 90.3565 135.184 98.4981C131.811 106.64 126.868 114.037 120.637 120.269C114.406 126.5 107.008 131.443 98.8666 134.815C90.725 138.187 81.999 139.923 73.1866 139.923C64.3742 139.923 55.6481 138.187 47.5066 134.815C39.365 131.443 31.9674 126.5 25.7361 120.269C19.5048 114.037 14.5619 106.64 11.1895 98.4981C7.81717 90.3565 6.08144 81.6304 6.08144 72.818C6.08144 64.0057 7.81717 55.2796 11.1895 47.138C14.5619 38.9964 19.5048 31.5988 25.7361 25.3675C31.9674 19.1363 39.365 14.1933 47.5066 10.821C55.6481 7.44863 64.3742 5.7129 73.1866 5.7129L73.1866 5.7129Z" stroke="#e4e4f2" stroke-width="4.89873"/>
<path d="M73.5 23.4494C79.9414 23.4494 86.3198 24.7181 92.2709 27.1831C98.222 29.6482 103.629 33.2612 108.184 37.816C112.739 42.3707 116.352 47.778 118.817 53.7291C121.282 59.6802 122.551 66.0586 122.551 72.5C122.551 78.9414 121.282 85.3198 118.817 91.2709C116.352 97.222 112.739 102.629 108.184 107.184C103.629 111.739 98.222 115.352 92.2709 117.817C86.3198 120.282 79.9414 121.551 73.5 121.551C67.0586 121.551 60.6802 120.282 54.7291 117.817C48.778 115.352 43.3707 111.739 38.816 107.184C34.2612 102.629 30.6481 97.222 28.1831 91.2709C25.7181 85.3198 24.4494 78.9414 24.4494 72.5C24.4494 66.0586 25.7181 59.6802 28.1831 53.7291C30.6481 47.778 34.2612 42.3707 38.816 37.816C43.3707 33.2612 48.778 29.6481 54.7291 27.1831C60.6802 24.7181 67.0586 23.4494 73.5 23.4494L73.5 23.4494Z" stroke="#e4e4f2" stroke-width="4.89873"/>
<path d="M73 24C84.3364 24 95.3221 27.9307 104.085 35.1225C112.848 42.3142 118.847 52.322 121.058 63.4406C123.27 74.5592 121.558 86.1006 116.214 96.0984C110.87 106.096 102.225 113.932 91.7515 118.27C81.278 122.608 69.6243 123.181 58.7761 119.89C47.9278 116.599 38.5562 109.649 32.258 100.223C25.9598 90.7971 23.1248 79.479 24.2359 68.1972C25.3471 56.9153 30.3357 46.3678 38.3518 38.3518" stroke="url(#paint0_linear_622:13617)" stroke-width="10" stroke-linecap="round"/>
<path d="M73 5.00001C84.365 5.00001 95.5488 7.84852 105.529 13.2852C115.509 18.7218 123.968 26.5732 130.131 36.1217C136.295 45.6702 139.967 56.6112 140.812 67.9448C141.657 79.2783 139.648 90.6429 134.968 101C130.288 111.357 123.087 120.375 114.023 127.232C104.96 134.088 94.3218 138.563 83.0824 140.248C71.8431 141.933 60.3606 140.775 49.6845 136.878C39.0085 132.981 29.4793 126.471 21.9681 117.942" stroke="url(#paint1_linear_622:13617)" stroke-width="10" stroke-linecap="round"/>
<path d="M9.60279 97.5926C6.37325 89.2671 4.81515 80.3871 5.01745 71.4595C5.21975 62.5319 7.1785 53.7316 10.7818 45.561C14.3852 37.3904 19.5626 30.0095 26.0184 23.8398C32.4742 17.6701 40.082 12.8323 48.4075 9.6028" stroke="url(#paint2_linear_622:13617)" stroke-width="10" stroke-linecap="round"/>
<path d="M73 5.00001C86.6504 5.00001 99.9849 9.10831 111.269 16.7904" stroke="url(#paint3_linear_622:13617)" stroke-width="10" stroke-linecap="round"/>
<circle cx="73.5" cy="72.5" r="29" fill="#e4e4f2" stroke="#e4e4f2"/>
<path d="M74 82.8332C68.0167 82.8332 63.1666 77.9831 63.1666 71.9998C63.1666 66.0166 68.0167 61.1665 74 61.1665C79.9832 61.1665 84.8333 66.0166 84.8333 71.9998C84.8333 77.9831 79.9832 82.8332 74 82.8332ZM74 80.6665C76.2985 80.6665 78.5029 79.7534 80.1282 78.1281C81.7535 76.5028 82.6666 74.2984 82.6666 71.9998C82.6666 69.7013 81.7535 67.4969 80.1282 65.8716C78.5029 64.2463 76.2985 63.3332 74 63.3332C71.7014 63.3332 69.497 64.2463 67.8717 65.8716C66.2464 67.4969 65.3333 69.7013 65.3333 71.9998C65.3333 74.2984 66.2464 76.5028 67.8717 78.1281C69.497 79.7534 71.7014 80.6665 74 80.6665ZM70.75 67.6665H77.25L79.9583 71.4582L74 77.4165L68.0416 71.4582L70.75 67.6665ZM71.8658 69.8332L70.8691 71.2307L74 74.3615L77.1308 71.2307L76.1341 69.8332H71.8658Z" fill="#6A6A9F"/>
<defs>
<linearGradient id="paint0_linear_622:13617" x1="45.9997" y1="19" x2="46.0897" y2="124.308" gradientUnits="userSpaceOnUse">
<stop stop-color="#E323FF"/>
<stop offset="1" stop-color="#7517F8"/>
</linearGradient>
<linearGradient id="paint1_linear_622:13617" x1="1.74103e-06" y1="8.70228e-06" x2="6.34739e-08" y2="140.677" gradientUnits="userSpaceOnUse">
<stop stop-color="#4DFFDF"/>
<stop offset="1" stop-color="#4DA1FF"/>
</linearGradient>
<linearGradient id="paint2_linear_622:13617" x1="36.4997" y1="5.07257e-06" x2="36.6213" y2="142.36" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFD422"/>
<stop offset="1" stop-color="#FF7D05"/>
</linearGradient>
<linearGradient id="paint3_linear_622:13617" x1="1.74103e-06" y1="8.70228e-06" x2="6.34739e-08" y2="140.677" gradientUnits="userSpaceOnUse">
<stop stop-color="#4DFFDF"/>
<stop offset="1" stop-color="#4DA1FF"/>
</linearGradient>
</defs>
</svg>
<div>
<h5 class="text-xl text-gray-600 text-center">Global Activities</h5>
<div class="mt-2 flex justify-center gap-4">
<h3 class="text-3xl font-bold text-gray-700">$23,988</h3>
<div class="flex items-end gap-1 text-green-500">
<svg class="w-3" viewBox="0 0 12 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.00001 0L12 8H-3.05176e-05L6.00001 0Z" fill="currentColor"/>
</svg>
<span>2%</span>
</div>
</div>
<span class="block text-center text-gray-500">Compared to last week $13,988</span>
</div>
<table class="w-full text-gray-600">
<tbody>
<tr>
<td class="py-2">Tailored ui</td>
<td class="text-gray-500">896</td>
<td>
<svg class="w-16 ml-auto" viewBox="0 0 68 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.4" width="17" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="19" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="35" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="51" width="17" height="21" rx="1" fill="#e4e4f2"/>
<path d="M0 7C8.62687 7 7.61194 16 17.7612 16C27.9104 16 25.3731 9 34 9C42.6269 9 44.5157 5 51.2537 5C57.7936 5 59.3731 14.5 68 14.5" stroke="url(#paint0_linear_622:13631)" stroke-width="2" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear_622:13631" x1="68" y1="7.74997" x2="1.69701" y2="8.10029" gradientUnits="userSpaceOnUse">
<stop stop-color="#E323FF"/>
<stop offset="1" stop-color="#7517F8"/>
</linearGradient>
</defs>
</svg>
</td>
</tr>
<tr>
<td class="py-2">Customize</td>
<td class="text-gray-500">1200</td>
<td>
<svg class="w-16 ml-auto" viewBox="0 0 68 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.4" width="17" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="19" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="35" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="51" width="17" height="21" rx="1" fill="#e4e4f2"/>
<path d="M0 12.929C8.69077 12.929 7.66833 9.47584 17.8928 9.47584C28.1172 9.47584 25.5611 15.9524 34.2519 15.9524C42.9426 15.9524 44.8455 10.929 51.6334 10.929C58.2217 10.929 59.3092 5 68 5" stroke="url(#paint0_linear_622:13640)" stroke-width="2" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear_622:13640" x1="34" y1="5" x2="34" y2="15.9524" gradientUnits="userSpaceOnUse">
<stop stop-color="#8AFF6C"/>
<stop offset="1" stop-color="#02C751"/>
</linearGradient>
</defs>
</svg>
</td>
</tr>
<tr>
<td class="py-2">Other</td>
<td class="text-gray-500">12</td>
<td>
<svg class="w-16 ml-auto" viewBox="0 0 68 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.4" width="17" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="19" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="35" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="51" width="17" height="21" rx="1" fill="#e4e4f2"/>
<path d="M0 6C8.62687 6 6.85075 12.75 17 12.75C27.1493 12.75 25.3731 9 34 9C42.6269 9 42.262 13.875 49 13.875C55.5398 13.875 58.3731 6 67 6" stroke="url(#paint0_linear_622:13649)" stroke-width="2" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear_622:13649" x1="67" y1="7.96873" x2="1.67368" y2="8.44377" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFD422"/>
<stop offset="1" stop-color="#FF7D05"/>
</linearGradient>
</defs>
</svg>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div>
<div class="h-full py-6 px-6 rounded-xl border border-gray-200 bg-white">
<h5 class="text-xl text-gray-700">Downloads</h5>
<div class="my-8">
<h1 class="text-5xl font-bold text-gray-800">64,5%</h1>
<span class="text-gray-500">Compared to last week $13,988</span>
</div>
<svg class="w-full" viewBox="0 0 218 69" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 67.5C27.8998 67.5 24.6002 15 52.5 15C80.3998 15 77.1002 29 105 29C132.9 29 128.6 52 156.5 52C184.4 52 189.127 63.8158 217.027 63.8158" stroke="url(#paint0_linear_622:13664)" stroke-width="3" stroke-linecap="round"/>
<path d="M0 67.5C27.2601 67.5 30.7399 31 58 31C85.2601 31 80.7399 2 108 2C135.26 2 134.74 43 162 43C189.26 43 190.74 63.665 218 63.665" stroke="url(#paint1_linear_622:13664)" stroke-width="3" stroke-linecap="round"/>
<defs>
<linearGradient id="paint0_linear_622:13664" x1="217.027" y1="15" x2="7.91244" y2="15" gradientUnits="userSpaceOnUse">
<stop stop-color="#4DFFDF"/>
<stop offset="1" stop-color="#4DA1FF"/>
</linearGradient>
<linearGradient id="paint1_linear_622:13664" x1="218" y1="18.3748" x2="5.4362" y2="18.9795" gradientUnits="userSpaceOnUse">
<stop stop-color="#E323FF"/>
<stop offset="1" stop-color="#7517F8"/>
</linearGradient>
</defs>
</svg>
<table class="mt-6 -mb-2 w-full text-gray-600">
<tbody>
<tr>
<td class="py-2">From new users</td>
<td class="text-gray-500">896</td>
<td>
<svg class="w-16 ml-auto" viewBox="0 0 68 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.4" width="17" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="19" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="35" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="51" width="17" height="21" rx="1" fill="#e4e4f2"/>
<path d="M0 7C8.62687 7 7.61194 16 17.7612 16C27.9104 16 25.3731 9 34 9C42.6269 9 44.5157 5 51.2537 5C57.7936 5 59.3731 14.5 68 14.5" stroke="url(#paint0_linear_622:13631)" stroke-width="2" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear_622:13631" x1="68" y1="7.74997" x2="1.69701" y2="8.10029" gradientUnits="userSpaceOnUse">
<stop stop-color="#E323FF"/>
<stop offset="1" stop-color="#7517F8"/>
</linearGradient>
</defs>
</svg>
</td>
</tr>
<tr>
<td class="py-2">From old users</td>
<td class="text-gray-500">1200</td>
<td>
<svg class="w-16 ml-auto" viewBox="0 0 68 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.4" width="17" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="19" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="35" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="51" width="17" height="21" rx="1" fill="#e4e4f2"/>
<path d="M0 12.929C8.69077 12.929 7.66833 9.47584 17.8928 9.47584C28.1172 9.47584 25.5611 15.9524 34.2519 15.9524C42.9426 15.9524 44.8455 10.929 51.6334 10.929C58.2217 10.929 59.3092 5 68 5" stroke="url(#paint0_linear_622:13640)" stroke-width="2" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear_622:13640" x1="34" y1="5" x2="34" y2="15.9524" gradientUnits="userSpaceOnUse">
<stop stop-color="#8AFF6C"/>
<stop offset="1" stop-color="#02C751"/>
</linearGradient>
</defs>
</svg>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div>
<div class="lg:h-full py-8 px-6 text-gray-600 rounded-xl border border-gray-200 bg-white">
<svg class="w-40 m-auto" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M27.9985 2.84071C31.2849 2.84071 34.539 3.488 37.5752 4.74562C40.6113 6.00324 43.3701 7.84657 45.6938 10.1703C48.0176 12.4941 49.861 15.2529 51.1186 18.289C52.3762 21.3252 53.0235 24.5793 53.0235 27.8657C53.0235 31.152 52.3762 34.4061 51.1186 37.4423C49.861 40.4785 48.0176 43.2372 45.6938 45.561C43.3701 47.8848 40.6113 49.7281 37.5752 50.9857C34.539 52.2433 31.2849 52.8906 27.9985 52.8906C24.7122 52.8906 21.4581 52.2433 18.4219 50.9857C15.3857 49.7281 12.627 47.8848 10.3032 45.561C7.97943 43.2372 6.1361 40.4785 4.87848 37.4423C3.62086 34.4061 2.97357 31.152 2.97357 27.8657C2.97357 24.5793 3.62086 21.3252 4.87848 18.289C6.13611 15.2529 7.97943 12.4941 10.3032 10.1703C12.627 7.84656 15.3857 6.00324 18.4219 4.74562C21.4581 3.488 24.7122 2.84071 27.9985 2.84071L27.9985 2.84071Z" stroke="#e4e4f2" stroke-width="3"/>
<path d="M27.301 2.50958C33.0386 2.35225 38.6614 4.13522 43.26 7.57004C47.8585 11.0049 51.1637 15.8907 52.641 21.437C54.1182 26.9834 53.6811 32.8659 51.4002 38.133C49.1194 43.4001 45.1283 47.7437 40.0726 50.4611C35.0169 53.1785 29.1923 54.1108 23.541 53.1071C17.8897 52.1034 12.7423 49.2225 8.93145 44.9305C5.12062 40.6384 2.86926 35.1861 2.54159 29.4558C2.21391 23.7254 3.82909 18.0521 7.12582 13.3536" stroke="url(#paint0_linear_622:13696)" stroke-width="5" stroke-linecap="round"/>
<path d="M13.3279 30.7467C13.3912 29.48 13.8346 28.5047 14.6579 27.8207C15.4939 27.124 16.5896 26.7757 17.9449 26.7757C18.8696 26.7757 19.6612 26.9404 20.3199 27.2697C20.9786 27.5864 21.4726 28.0234 21.8019 28.5807C22.1439 29.1254 22.3149 29.746 22.3149 30.4427C22.3149 31.2407 22.1059 31.9184 21.6879 32.4757C21.2826 33.0204 20.7949 33.3877 20.2249 33.5777V33.6537C20.9596 33.8817 21.5296 34.287 21.9349 34.8697C22.3529 35.4524 22.5619 36.1997 22.5619 37.1117C22.5619 37.8717 22.3846 38.5494 22.0299 39.1447C21.6879 39.74 21.1749 40.2087 20.4909 40.5507C19.8196 40.88 19.0089 41.0447 18.0589 41.0447C16.6276 41.0447 15.4622 40.6837 14.5629 39.9617C13.6636 39.2397 13.1886 38.1757 13.1379 36.7697H15.7219C15.7472 37.3904 15.9562 37.8907 16.3489 38.2707C16.7542 38.638 17.3052 38.8217 18.0019 38.8217C18.6479 38.8217 19.1419 38.6444 19.4839 38.2897C19.8386 37.9224 20.0159 37.4537 20.0159 36.8837C20.0159 36.1237 19.7752 35.579 19.2939 35.2497C18.8126 34.9204 18.0652 34.7557 17.0519 34.7557H16.5009V32.5707H17.0519C18.8506 32.5707 19.7499 31.969 19.7499 30.7657C19.7499 30.221 19.5852 29.7967 19.2559 29.4927C18.9392 29.1887 18.4769 29.0367 17.8689 29.0367C17.2736 29.0367 16.8112 29.2014 16.4819 29.5307C16.1652 29.8474 15.9816 30.2527 15.9309 30.7467H13.3279ZM25.6499 37.9477C26.8659 36.9344 27.8349 36.092 28.5569 35.4207C29.2789 34.7367 29.8806 34.0274 30.3619 33.2927C30.8433 32.558 31.0839 31.836 31.0839 31.1267C31.0839 30.4807 30.9319 29.974 30.6279 29.6067C30.3239 29.2394 29.8553 29.0557 29.2219 29.0557C28.5886 29.0557 28.1009 29.271 27.7589 29.7017C27.4169 30.1197 27.2396 30.696 27.2269 31.4307H24.6429C24.6936 29.9107 25.1433 28.758 25.9919 27.9727C26.8533 27.1874 27.9426 26.7947 29.2599 26.7947C30.7039 26.7947 31.8123 27.181 32.5849 27.9537C33.3576 28.7137 33.7439 29.7207 33.7439 30.9747C33.7439 31.9627 33.4779 32.9064 32.9459 33.8057C32.4139 34.705 31.8059 35.4904 31.1219 36.1617C30.4379 36.8204 29.5449 37.6184 28.4429 38.5557H34.0479V40.7597H24.6619V38.7837L25.6499 37.9477Z" fill="currentColor"/>
<path d="M36.1948 28.8842C36.1948 29.4438 36.2557 29.8634 36.3775 30.1432C36.4992 30.423 36.6967 30.5628 36.9699 30.5628C37.5097 30.5628 37.7796 30.0033 37.7796 28.8842C37.7796 27.7717 37.5097 27.2155 36.9699 27.2155C36.6967 27.2155 36.4992 27.3537 36.3775 27.6302C36.2557 27.9067 36.1948 28.3247 36.1948 28.8842ZM38.456 28.8842C38.456 29.6347 38.3293 30.2024 38.0758 30.5875C37.8257 30.9693 37.457 31.1602 36.9699 31.1602C36.5091 31.1602 36.1504 30.9644 35.8936 30.5727C35.6402 30.181 35.5135 29.6182 35.5135 28.8842C35.5135 28.1371 35.6352 27.5742 35.8788 27.1957C36.1257 26.8172 36.4894 26.6279 36.9699 26.6279C37.4472 26.6279 37.8142 26.8238 38.0709 27.2155C38.3276 27.6071 38.456 28.1634 38.456 28.8842ZM40.5395 31.7774C40.5395 32.3402 40.6003 32.7615 40.7221 33.0413C40.8439 33.3178 41.043 33.456 41.3195 33.456C41.596 33.456 41.8001 33.3194 41.9317 33.0462C42.0634 32.7697 42.1292 32.3468 42.1292 31.7774C42.1292 31.2145 42.0634 30.7982 41.9317 30.5283C41.8001 30.2551 41.596 30.1185 41.3195 30.1185C41.043 30.1185 40.8439 30.2551 40.7221 30.5283C40.6003 30.7982 40.5395 31.2145 40.5395 31.7774ZM42.8056 31.7774C42.8056 32.5245 42.6789 33.0906 42.4254 33.4757C42.1753 33.8575 41.8067 34.0484 41.3195 34.0484C40.8521 34.0484 40.4917 33.8526 40.2383 33.4609C39.9881 33.0693 39.8631 32.5081 39.8631 31.7774C39.8631 31.0302 39.9849 30.4674 40.2284 30.0889C40.4753 29.7104 40.839 29.5211 41.3195 29.5211C41.7869 29.5211 42.1506 29.7153 42.4106 30.1037C42.6739 30.4888 42.8056 31.0467 42.8056 31.7774ZM41.5318 26.7316L37.5278 33.9497H36.8021L40.8061 26.7316H41.5318Z" fill="white"/>
<path d="M23.5776 18.4198H25.5424V22.8407H23.5776V18.4198ZM30.4545 16.455H32.4193V22.8407H30.4545V16.455ZM27.0161 13.5078H28.9809V22.8407H27.0161V13.5078Z" fill="#6A6A9F"/>
<defs>
<linearGradient id="paint0_linear_622:13696" x1="5.54791e-07" y1="42.0001" x2="54.6039" y2="41.9535" gradientUnits="userSpaceOnUse">
<stop stop-color="#E323FF"/>
<stop offset="1" stop-color="#7517F8"/>
</linearGradient>
</defs>
</svg>
<div class="mt-6">
<h5 class="text-xl text-gray-700 text-center">Ask to customize</h5>
<div class="mt-2 flex justify-center gap-4">
<h3 class="text-3xl font-bold text-gray-700">28</h3>
<div class="flex items-end gap-1 text-green-500">
<svg class="w-3" viewBox="0 0 12 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.00001 0L12 8H-3.05176e-05L6.00001 0Z" fill="currentColor"/>
</svg>
<span>2%</span>
</div>
</div>
<span class="block text-center text-gray-500">Compared to last week 13</span>
</div>
<table class="mt-6 -mb-2 w-full text-gray-600">
<tbody>
<tr>
<td class="py-2">Tailored ui</td>
<td class="text-gray-500">896</td>
<td>
<svg class="w-16 ml-auto" viewBox="0 0 68 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.4" width="17" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="19" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="35" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="51" width="17" height="21" rx="1" fill="#e4e4f2"/>
<path d="M0 7C8.62687 7 7.61194 16 17.7612 16C27.9104 16 25.3731 9 34 9C42.6269 9 44.5157 5 51.2537 5C57.7936 5 59.3731 14.5 68 14.5" stroke="url(#paint0_linear_622:13631)" stroke-width="2" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear_622:13631" x1="68" y1="7.74997" x2="1.69701" y2="8.10029" gradientUnits="userSpaceOnUse">
<stop stop-color="#E323FF"/>
<stop offset="1" stop-color="#7517F8"/>
</linearGradient>
</defs>
</svg>
</td>
</tr>
<tr>
<td class="py-2">Customize</td>
<td class="text-gray-500">1200</td>
<td>
<svg class="w-16 ml-auto" viewBox="0 0 68 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.4" width="17" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="19" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="35" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="51" width="17" height="21" rx="1" fill="#e4e4f2"/>
<path d="M0 12.929C8.69077 12.929 7.66833 9.47584 17.8928 9.47584C28.1172 9.47584 25.5611 15.9524 34.2519 15.9524C42.9426 15.9524 44.8455 10.929 51.6334 10.929C58.2217 10.929 59.3092 5 68 5" stroke="url(#paint0_linear_622:13640)" stroke-width="2" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear_622:13640" x1="34" y1="5" x2="34" y2="15.9524" gradientUnits="userSpaceOnUse">
<stop stop-color="#8AFF6C"/>
<stop offset="1" stop-color="#02C751"/>
</linearGradient>
</defs>
</svg>
</td>
</tr>
<tr>
<td class="py-2">Other</td>
<td class="text-gray-500">12</td>
<td>
<svg class="w-16 ml-auto" viewBox="0 0 68 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.4" width="17" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="19" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="35" width="14" height="21" rx="1" fill="#e4e4f2"/>
<rect opacity="0.4" x="51" width="17" height="21" rx="1" fill="#e4e4f2"/>
<path d="M0 6C8.62687 6 6.85075 12.75 17 12.75C27.1493 12.75 25.3731 9 34 9C42.6269 9 42.262 13.875 49 13.875C55.5398 13.875 58.3731 6 67 6" stroke="url(#paint0_linear_622:13649)" stroke-width="2" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear_622:13649" x1="67" y1="7.96873" x2="1.67368" y2="8.44377" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFD422"/>
<stop offset="1" stop-color="#FF7D05"/>
</linearGradient>
</defs>
</svg>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
</script>
<style lang="scss" scoped>
</style>

160
web/src/views/Home.vue Normal file
View File

@@ -0,0 +1,160 @@
<template>
<div class="flex flex-col h-screen w-screen bg-diy">
<header class="bg-zinc-700 text-white py-4 fixed top-0 w-full z-10">
<div class="container flex justify-between items-center px-4">
<div class="flex items-center">
<img class="h-8" src="../assets/logo.svg" alt="Logo">
<h1 class="ml-2 text-l">opencatd-open</h1>
</div>
<div class="flex justify-between items-center space-x-2">
<a href="https://github.com/mirrors2/opencatd-open">
<img class="h-6" alt="GitHub Repo stars"
src="https://img.shields.io/github/stars/mirrors2/opencatd-open?style=social">
</a>
<!-- <button class="bg-white text-gray-900 font-medium py-2 px-4 rounded-full hover:bg-gray-200 hover:text-gray-900">Log In</button> -->
</div>
</div>
</header>
<main class="w-screen flex-grow flex flex-col justify-center items-center bg-zinc-700 mt-16">
<div class="">
<div class=" flex items-center justify-center my-0">
<img src="../assets/logo.svg" alt="Project Logo" class="logo h-10 my-0">
</div>
<div class=" flex items-center justify-center my-0">
<a class="text-gray-300 text-4xl" href="https://github.com/mirrors2/opencatd-open">opencatd-open</a>
</div>
<div class="my-12">
<p class="text-gray-300 mt-2 mx-5">opencatd-open is an open-source, team-shared service for ChatGPT API that can
be safely shared with others for API usage.</p>
</div>
</div>
<div class="mt-8 shadow-lg mb-8 flax justify-center items-center">
<div class="flex gap-2">
<div>
<input class="w-60 py-2 px-2 bg-zinc-700 rounded-lg border focus:outline-none text-white flex-1" disabled name=""
id="" v-model="url">
</div>
<br>
<div>
<input type="submit" value="复制" @click="copyUrl"
class="bg-white font-medium py-2 px-3 hover:bg-gray-200 hover:text-gray-900 rounded-lg h-10 text-gray-700">
</div>
</div>
</div>
<div class="bg-diy w-screen h-auto flex flex-col overflow-x-auto justify-center items-center">
<div>
<p class="text-gray-500 mt-5 text-sm">👉Api-Keys: <a
href=https://platform.openai.com/account/api-keys>https://platform.openai.com/account/api-keys</a></p>
</div>
<section
class="w-10/12 h-auto mt-5 my-4 shadow-lg mb-8 p-12 bg-white border-2 border-gray-200 rounded-xl hover:shadow-2xl">
<!-- card content -->
<div class="flex justify-center">
<h1 class="text-4xl my-2">使用说明</h1>
</div>
<hr class="my-5">
<div class="text-xl mt-5">
<h2>作为OpenAI API代理</h2>
</div>
<div class="my-4 gap">
<p>由于OpenAI API不能再国内访问使用"openai api key+自定义域名"可以无感访问</p>
<p>在自定义地址中填入当前地址</p> <img class=" sm:max-w-full md:max-w-sm h-auto" src="../assets/usersdomain.jpg" alt="">
</div>
</section>
</div>
<div class="bg-diy w-screen h-auto flex flex-grow overflow-x-auto justify-center">
<section
class="w-10/12 h-auto my-2 shadow-lg mb-8 p-12 bg-white border-2 border-gray-200 rounded-xl hover:shadow-2xl">
<!-- card content -->
<div class="text-xl">
<h2>团队共享API模式</h2>
</div>
<div class="my-4 gap">
<p>团队共享模式可以把openai api key分发给多人使用.使用openai api key作为内部访问密钥</p>
<p>系统生成api-key,使用"系统生成的api-key+自定义域名"可以无感访问</p>
<p></p>
<p>在自定义地址中填入当前地址<span class="text-rose-500">OpenCat</span>为例(目前体验最好):</p> <img
class=" sm:max-w-full md:max-w-sm h-auto" src="../assets/team.jpg" alt="">
<hr class="my-5">
<blockquote>
<p>注意:第三方应用需要支持自定义 OpenAI Key Host</p>
</blockquote>
</div>
</section>
</div>
</main>
<footer class="bg-diy py-6 w-screen flex flex-col justify-center items-center">
<div class="bg-diy w-10/12 h-auto flex overflow-x-auto justify-between mx-60">
<ul class="flex space-x-4">
<li><a href="https://github.com/mirrors2/opencatd-open#qa" class="text-gray-700 hover:text-gray-900">FAQ</a>
</li>
</ul>
<p class="text-gray-700">© {{ currentYear }} <a href="https://github/mirrors2/opencatd-open">Sakurasan</a>. All
Rights
Reserved.</p>
</div>
</footer>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
const currentYear = ref('');
let url = ref('')
const copyUrl = () => {
navigator.clipboard.writeText(url.value).then(() => {
alert('复制成功!')
}, () => {
alert('复制失败,请手动复制。')
})
}
const getcurrentYear = () => {
currentYear.value = new Date().getFullYear().toString()
}
onMounted(() => {
url.value = window.location.origin;
getcurrentYear();
})
</script>
<style scoped>
.bg-diy {
background-color: #f0f0f0;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 3em #45f5e3aa);
}
p {
margin-bottom: 4px;
}
blockquote {
padding: 0 1em;
border-left: 0.25em solid #838989aa;
}
</style>

56
web/src/views/Signin.vue Normal file
View File

@@ -0,0 +1,56 @@
<template>
<!--
This example requires updating your template:
```
<html class="h-full bg-white">
<body class="h-full">
```
-->
<div class="flex min-h-full flex-1 flex-col justify-center px-6 py-12 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
<div class="flex items-center justify-center">
<img class="mx-0 h-10 w-auto" src="../assets/logo.svg"
alt="opencatd-open" />
</div>
<h2 class="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">Sign in to your account
</h2>
</div>
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
<form class="space-y-6" action="#" method="POST">
<div>
<label for="email" class="block text-sm font-medium leading-6 text-gray-900">User Name</label>
<div class="mt-2">
<input id="email" name="email" type="email" autocomplete="email" required=""
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" />
</div>
</div>
<div>
<div class="flex items-center justify-between">
<label for="password" class="block text-sm font-medium leading-6 text-gray-900">Password</label>
<div class="text-sm">
</div>
</div>
<div class="mt-2">
<input id="password" name="password" type="password" autocomplete="current-password" required=""
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" />
</div>
</div>
<div>
<button type="submit"
class="flex w-full justify-center rounded-md bg-cyan-500 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-cyan-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Sign
in</button>
</div>
</form>
</div>
</div>
</template>
<script setup>
</script>