Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3920f11fa | ||
|
|
4fae477af8 | ||
|
|
dd2d583ffc | ||
|
|
27742362f8 | ||
|
|
c0bcc74c97 | ||
|
|
0d01ab4617 | ||
|
|
3db8ebe34e | ||
|
|
08ce7e2a4b | ||
|
|
752053f7c4 | ||
|
|
151757c2fe | ||
|
|
8dbeeee9bf | ||
|
|
3b609324b8 | ||
|
|
c37760c78b | ||
|
|
4cd019e11e | ||
|
|
8d6c90c550 | ||
|
|
ec073db5c7 | ||
|
|
b61e85b7fc | ||
|
|
f336eba19a | ||
|
|
68eb6f5ce1 | ||
|
|
c838e7be16 | ||
|
|
e8ed9aaa78 | ||
|
|
e322e09833 | ||
|
|
409bfe2cef | ||
|
|
35f0ef3aab | ||
|
|
4b79bca94f | ||
|
|
11173abc02 | ||
|
|
9c03c2d2e2 | ||
|
|
aa1d00cd55 | ||
|
|
545147abe0 | ||
|
|
678928cafd | ||
|
|
0255a470c1 | ||
|
|
b3b4f6bba1 | ||
|
|
a481a0eb7e | ||
|
|
a0155fc0b1 | ||
|
|
adbc388920 | ||
|
|
e110eb28ee | ||
|
|
c47b540664 | ||
|
|
c3000c401c | ||
|
|
d99407127f | ||
|
|
c135d1c022 | ||
|
|
bf373c79ae | ||
|
|
c9b763b9ed | ||
|
|
ec31eba72f | ||
|
|
9c5ac69024 | ||
|
|
516354c79a | ||
|
|
933a70a474 | ||
|
|
06bd5e048b | ||
|
|
f4e947b377 | ||
|
|
bc39ce1ddd | ||
|
|
bae47a43ad | ||
|
|
a2e929890b |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@ bin/
|
|||||||
test/
|
test/
|
||||||
*.log
|
*.log
|
||||||
*.db
|
*.db
|
||||||
|
demo/
|
||||||
15
README.md
15
README.md
@@ -10,11 +10,15 @@ OpenCat for Team的开源实现
|
|||||||
|
|
||||||
~~基本~~实现了opencatd的全部功能
|
~~基本~~实现了opencatd的全部功能
|
||||||
|
|
||||||
|
(openai附属能力:whisper,tts,dall-e(text to image)...)
|
||||||
|
|
||||||
## Extra Support:
|
## Extra Support:
|
||||||
|
|
||||||
| 任务 | 完成情况 |
|
| 任务 | 完成情况 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|[Azure OpenAI](./doc/azure.md) | ✅|
|
|[Azure OpenAI](./doc/azure.md) | ✅|
|
||||||
|
|[Claude](./doc/azure.md) | ✅|
|
||||||
|
|[Gemini](./doc/gemini.md) | ✅|
|
||||||
| ... | ... |
|
| ... | ... |
|
||||||
|
|
||||||
|
|
||||||
@@ -50,6 +54,11 @@ wget https://github.com/mirrors2/opencatd-open/raw/main/docker/docker-compose.ym
|
|||||||
>重置 root 的 token
|
>重置 root 的 token
|
||||||
- `docker exec opencatd-open opencatd reset_root`
|
- `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
|
## Q&A
|
||||||
关于证书?
|
关于证书?
|
||||||
@@ -65,6 +74,12 @@ wget https://github.com/mirrors2/opencatd-open/raw/main/docker/docker-compose.ym
|
|||||||
|
|
||||||
修改openai的endpoint地址?使用任意上游地址(套娃代理)
|
修改openai的endpoint地址?使用任意上游地址(套娃代理)
|
||||||
- 设置环境变量 openai_endpoint
|
- 设置环境变量 openai_endpoint
|
||||||
|
|
||||||
|
使用Nginx + Docker部署
|
||||||
|
- [使用Nginx + Docker部署](./doc/deploy.md)
|
||||||
|
|
||||||
|
pandora for team
|
||||||
|
- [pandora for team](./doc/pandora.md)
|
||||||
# License
|
# License
|
||||||
|
|
||||||
[GNU General Public License v3.0](License)
|
[GNU General Public License v3.0](License)
|
||||||
10
doc/API.md
10
doc/API.md
@@ -115,8 +115,9 @@ Resp:
|
|||||||
### 重置用户 Token
|
### 重置用户 Token
|
||||||
|
|
||||||
- URL: `/1/users/:id/reset`
|
- URL: `/1/users/:id/reset`
|
||||||
|
- URL: `/1/users/:id/reset?token={new user token}`
|
||||||
- Method: `POST`
|
- Method: `POST`
|
||||||
- Description: 重置用户 Token
|
- Description: 重置用户 Token 默认生成新 Token 也可以指定
|
||||||
- Headers:
|
- Headers:
|
||||||
- Authorization: Bearer {token}
|
- Authorization: Bearer {token}
|
||||||
|
|
||||||
@@ -180,7 +181,7 @@ Req:
|
|||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
api_type:不传的话默认为“openai”;当前可选值[openai,azure_openai]
|
api_type:不传的话默认为“openai”;当前可选值[openai,azure,claude]
|
||||||
endpoint: 当 api_type 为 azure_openai时传入(目前暂未使用)
|
endpoint: 当 api_type 为 azure_openai时传入(目前暂未使用)
|
||||||
|
|
||||||
Resp:
|
Resp:
|
||||||
@@ -235,4 +236,7 @@ Resp:
|
|||||||
"totalUnit" : 55
|
"totalUnit" : 55
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Whisper接口
|
||||||
|
### 与openai一致
|
||||||
|
|||||||
BIN
doc/ama_pandora_chat_test.png
Normal file
BIN
doc/ama_pandora_chat_test.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
BIN
doc/ama_pandora_config_client.png
Normal file
BIN
doc/ama_pandora_config_client.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
BIN
doc/ama_pandora_copy_config.png
Normal file
BIN
doc/ama_pandora_copy_config.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
BIN
doc/ama_pandora_provider.png
Normal file
BIN
doc/ama_pandora_provider.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
@@ -8,6 +8,7 @@
|
|||||||
| model name | deployment name |
|
| model name | deployment name |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|gpt-35-turbo | gpt-35-turbo |
|
|gpt-35-turbo | gpt-35-turbo |
|
||||||
|
|gpt-35-turbo-16k | gpt-35-turbo-16k |
|
||||||
| gpt-4 | gpt-4 |
|
| gpt-4 | gpt-4 |
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
@@ -15,3 +16,10 @@
|
|||||||
- key name以 azure.[resource name]的方式添加
|
- key name以 azure.[resource name]的方式添加
|
||||||
- 密钥任取一个
|
- 密钥任取一个
|
||||||
- <img src="./azure_openai_for_team.png" alt="azure_openai_for_team" height="600">
|
- <img src="./azure_openai_for_team.png" alt="azure_openai_for_team" height="600">
|
||||||
|
- [AMA(问天)](http://bytemyth.com/ama) 使用方式
|
||||||
|
- 
|
||||||
|
- 每个 team server 用户旁边有一个复制按钮,点击后,把复制的链接粘贴到浏览器,可以一键设置
|
||||||
|
|
||||||
|
## Claude
|
||||||
|
|
||||||
|
- opencat 添加Claude api, key name以 "claude.key名称",即("Api类型.Key名称")
|
||||||
|
|||||||
BIN
doc/azure_ama.png
Normal file
BIN
doc/azure_ama.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
28
doc/deploy.md
Normal file
28
doc/deploy.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
# 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;
|
||||||
|
}
|
||||||
|
```
|
||||||
6
doc/gemini.md
Normal file
6
doc/gemini.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
## 添加ApiKey
|
||||||
|
gemini的"ApiType":"google"
|
||||||
|
|
||||||
|
或者使用 google.xxxx 的apikey名称 添加
|
||||||
|

|
||||||
|
|
||||||
BIN
doc/gemini_key.jpg
Normal file
BIN
doc/gemini_key.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
24
doc/pandora.md
Normal file
24
doc/pandora.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# 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.添加接口
|
||||||
|

|
||||||
|
|
||||||
|
2.创建用户&Copy Config
|
||||||
|

|
||||||
|
|
||||||
|
Ex:`ama://set-api-key?server=http%3A%2F%2F123.456.7.89&key=8fc322fa-15d2-43d7-bc59-621554e82c2a`
|
||||||
|
|
||||||
|
3.Configure Client
|
||||||
|

|
||||||
|
|
||||||
|
3.测试聊天
|
||||||
|

|
||||||
@@ -3,7 +3,7 @@ WORKDIR /frontend-build
|
|||||||
COPY ./web/ .
|
COPY ./web/ .
|
||||||
RUN npm install && npm run build && rm -rf node_modules
|
RUN npm install && npm run build && rm -rf node_modules
|
||||||
|
|
||||||
FROM golang:1.19-alpine as builder
|
FROM golang:1.21-alpine as builder
|
||||||
LABEL anther="github.com/Sakurasan"
|
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
|
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && apk --no-cache add make cmake upx
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
@@ -11,13 +11,13 @@ COPY --from=frontend /frontend-build/dist /build/dist
|
|||||||
COPY . /build
|
COPY . /build
|
||||||
ENV GO111MODULE=on
|
ENV GO111MODULE=on
|
||||||
# ENV GOPROXY=https://goproxy.cn,direct
|
# ENV GOPROXY=https://goproxy.cn,direct
|
||||||
CMD [ "go mod download" ]
|
CMD [ "go mod tidy","go mod download" ]
|
||||||
RUN make build
|
RUN make build
|
||||||
|
|
||||||
FROM alpine:latest AS runner
|
FROM alpine:latest AS runner
|
||||||
# 设置alpine 时间为上海时间
|
# 设置alpine 时间为上海时间
|
||||||
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 \
|
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 \
|
&& echo "Asia/Shanghai" > /etc/timezone
|
||||||
# RUN apk update && apk --no-cache add openssl libgcc libstdc++ binutils
|
# RUN apk update && apk --no-cache add openssl libgcc libstdc++ binutils
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder /build/bin/opencatd /app/opencatd
|
COPY --from=builder /build/bin/opencatd /app/opencatd
|
||||||
|
|||||||
113
go.mod
113
go.mod
@@ -1,51 +1,102 @@
|
|||||||
module opencatd-open
|
module opencatd-open
|
||||||
|
|
||||||
go 1.19
|
go 1.21
|
||||||
|
|
||||||
|
toolchain go1.21.9
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
cloud.google.com/go/vertexai v0.7.1
|
||||||
github.com/Sakurasan/to v0.0.0-20180919163141-e72657dd7c7d
|
github.com/Sakurasan/to v0.0.0-20180919163141-e72657dd7c7d
|
||||||
github.com/duke-git/lancet/v2 v2.1.19
|
github.com/duke-git/lancet/v2 v2.3.0
|
||||||
github.com/gin-gonic/gin v1.9.0
|
github.com/faiface/beep v1.1.0
|
||||||
github.com/glebarez/sqlite v1.7.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/google/uuid v1.3.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/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480
|
github.com/pkoukk/tiktoken-go v0.1.6
|
||||||
github.com/sashabaranov/go-openai v1.9.0
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
gorm.io/gorm v1.24.6
|
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
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic v1.8.0 // indirect
|
cloud.google.com/go v0.113.0 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
cloud.google.com/go/ai v0.5.0 // indirect
|
||||||
github.com/dlclark/regexp2 v1.8.1 // 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/dustin/go-humanize v1.0.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/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/glebarez/go-sqlite v1.20.3 // 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/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.11.2 // indirect
|
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.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/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 // 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/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.9 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
golang.org/x/crypto v0.5.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect
|
||||||
golang.org/x/net v0.7.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
|
||||||
golang.org/x/sys v0.5.0 // indirect
|
go.opentelemetry.io/otel v1.26.0 // indirect
|
||||||
golang.org/x/text v0.7.0 // indirect
|
go.opentelemetry.io/otel/metric v1.26.0 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // 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
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
modernc.org/libc v1.22.2 // indirect
|
modernc.org/libc v1.50.5 // indirect
|
||||||
modernc.org/mathutil v1.5.0 // indirect
|
modernc.org/mathutil v1.6.0 // indirect
|
||||||
modernc.org/memory v1.5.0 // indirect
|
modernc.org/memory v1.8.0 // indirect
|
||||||
modernc.org/sqlite v1.20.3 // indirect
|
modernc.org/sqlite v1.29.9 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
470
go.sum
470
go.sum
@@ -1,139 +1,435 @@
|
|||||||
|
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 h1:3v1QFdgk450QH+7C+lw1k+olbjK4fKGsrEfnEG/HLkY=
|
||||||
github.com/Sakurasan/to v0.0.0-20180919163141-e72657dd7c7d/go.mod h1:2sp0vsMyh5sqmKl5N+ps/cSspqLkoXUlesSzsufIGRU=
|
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.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
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/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
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-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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0=
|
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||||
github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/duke-git/lancet/v2 v2.1.19 h1:dbRB1m6wOMV1I0ax/3S6ngop8SYM6I7sr+7D9IXjS2E=
|
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||||
github.com/duke-git/lancet/v2 v2.1.19/go.mod h1:hNcc06mV7qr+crH/0nP+rlC3TB0Q9g5OrVnO8/TGD4c=
|
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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
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/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 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||||
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||||
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
|
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||||
github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=
|
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||||
github.com/glebarez/sqlite v1.7.0 h1:A7Xj/KN2Lvie4Z4rrgQHY8MsbebX3NyWsL3n2i82MVI=
|
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||||
github.com/glebarez/sqlite v1.7.0/go.mod h1:PkeevrRlF/1BhQBCnzcMWzgrIk7IOop+qS2jUYLfHhk=
|
github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
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/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
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/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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
|
||||||
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
|
github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
|
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||||
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
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 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
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/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 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
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.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
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 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
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/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 h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480 h1:IFhPCcB0/HtnEN+ZoUGDT55YgFCymbFJ15kXqs3nv5w=
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480/go.mod h1:BijIqAP84FMYC4XbdJgjyMpiSjusU8x0Y0W9K2t0QtU=
|
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 h1:VstopitMQi3hZP0fzvnsLmzXZdQGc4bEcgu24cp+d4M=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/sashabaranov/go-openai v1.9.0 h1:NoiO++IISxxJ1pRc0n7uZvMGMake0G+FJ1XPwXtprsA=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/sashabaranov/go-openai v1.9.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
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.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.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.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.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.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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
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/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
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=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc=
|
||||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
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/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
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=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
google.golang.org/api v0.173.0 h1:fz6B7GWYWLS/HfruiTsRYVKQQApJ6vasTYWAK6+Qo8g=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/api v0.173.0/go.mod h1:ins7pTzjeBPQ3SdC/plzki6d/dQWwAWy8qVZ4Vgkzl8=
|
||||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
google.golang.org/api v0.178.0 h1:yoW/QMI4bRVCHF+NWOTa4cL8MoWL3Jnuc7FlcFF91Ok=
|
||||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
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=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/vansante/go-ffprobe.v2 v2.1.1 h1:DIh5fMn+tlBvG7pXyUZdemVmLdERnf2xX6XOFF+0BBU=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/vansante/go-ffprobe.v2 v2.1.1/go.mod h1:qF0AlAjk7Nqzqf3y333Ly+KxN3cKF2JqA3JT5ZheUGE=
|
||||||
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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/gorm v1.24.6 h1:wy98aq9oFEetsc4CAbKD2SoBCdMzsbSIvSUUFJuHi5s=
|
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||||
gorm.io/gorm v1.24.6/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
|
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
||||||
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
|
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
modernc.org/libc v1.35.0 h1:EQ4szx6Q/QLZuysmAnI4dfRnKbAbNlENp23ruvTJ2nE=
|
||||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
modernc.org/libc v1.35.0/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
|
||||||
modernc.org/sqlite v1.20.3 h1:SqGJMMxjj1PHusLxdYxeQSodg7Jxn9WWkaAQjKrntZs=
|
modernc.org/libc v1.50.5 h1:ZzeUd0dIc/sUtoPTCYIrgypkuzoGzNu6kbEWj2VuEmk=
|
||||||
modernc.org/sqlite v1.20.3/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
|
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=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|||||||
85
opencat.go
85
opencat.go
@@ -1,7 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"embed"
|
"embed"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -9,6 +12,7 @@ import (
|
|||||||
"opencatd-open/store"
|
"opencatd-open/store"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/duke-git/lancet/v2/fileutil"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -29,6 +33,12 @@ func getFileSystem(path string) http.FileSystem {
|
|||||||
func main() {
|
func main() {
|
||||||
args := os.Args[1:]
|
args := os.Args[1:]
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
|
type user struct {
|
||||||
|
ID uint
|
||||||
|
Name string
|
||||||
|
Token string
|
||||||
|
}
|
||||||
|
var us []user
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "reset_root":
|
case "reset_root":
|
||||||
log.Println("reset root token...")
|
log.Println("reset root token...")
|
||||||
@@ -46,17 +56,84 @@ func main() {
|
|||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Println("new root token:", ntoken)
|
log.Println("[success]new root token:", ntoken)
|
||||||
return
|
return
|
||||||
case "root_token":
|
case "root_token":
|
||||||
log.Println("reset root token...")
|
log.Println("query root token...")
|
||||||
if user, err := store.GetUserByID(uint(1)); err != nil {
|
if user, err := store.GetUserByID(uint(1)); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
log.Println("root token:", user.Token)
|
log.Println("[success]root token:", user.Token)
|
||||||
return
|
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:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -71,6 +148,8 @@ func main() {
|
|||||||
// 获取当前用户信息
|
// 获取当前用户信息
|
||||||
group.GET("/me", router.HandleMe)
|
group.GET("/me", router.HandleMe)
|
||||||
|
|
||||||
|
group.GET("/me/usages", router.HandleMeUsage)
|
||||||
|
|
||||||
// 获取所有Key
|
// 获取所有Key
|
||||||
group.GET("/keys", router.HandleKeys)
|
group.GET("/keys", router.HandleKeys)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
https://learn.microsoft.com/zh-cn/azure/cognitive-services/openai/chatgpt-quickstart
|
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 \
|
curl $AZURE_OPENAI_ENDPOINT/openai/deployments/gpt-35-turbo/chat/completions?api-version=2023-03-15-preview \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
@@ -9,10 +10,15 @@ curl $AZURE_OPENAI_ENDPOINT/openai/deployments/gpt-35-turbo/chat/completions?api
|
|||||||
"messages": [{"role": "user", "content": "你好"}]
|
"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 \
|
curl $AZURE_OPENAI_ENDPOINT/openai/deployments?api-version=2022-12-01 \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-H "api-key: $AZURE_OPENAI_KEY" \
|
-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
|
package azureopenai
|
||||||
|
|||||||
284
pkg/claude/chat.go
Normal file
284
pkg/claude/chat.go
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
// 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
|
||||||
|
})
|
||||||
|
}
|
||||||
429
pkg/claude/claude.go
Normal file
429
pkg/claude/claude.go
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
244
pkg/google/chat.go
Normal file
244
pkg/google/chat.go
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
335
pkg/openai/chat.go
Normal file
335
pkg/openai/chat.go
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
149
pkg/openai/dall-e.go
Normal file
149
pkg/openai/dall-e.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
93
pkg/openai/tts.go
Normal file
93
pkg/openai/tts.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
181
pkg/tokenizer/tokenizer.go
Normal file
181
pkg/tokenizer/tokenizer.go
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
35
router/chat.go
Normal file
35
router/chat.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
472
router/router.go
472
router/router.go
@@ -3,27 +3,37 @@ package router
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"mime/multipart"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
"opencatd-open/pkg/azureopenai"
|
"opencatd-open/pkg/azureopenai"
|
||||||
|
"opencatd-open/pkg/claude"
|
||||||
|
oai "opencatd-open/pkg/openai"
|
||||||
|
"opencatd-open/pkg/tokenizer"
|
||||||
"opencatd-open/store"
|
"opencatd-open/store"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sakurasan/to"
|
"github.com/Sakurasan/to"
|
||||||
"github.com/duke-git/lancet/v2/cryptor"
|
"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/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkoukk/tiktoken-go"
|
|
||||||
"github.com/sashabaranov/go-openai"
|
"github.com/sashabaranov/go-openai"
|
||||||
|
"gopkg.in/vansante/go-ffprobe.v2"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -118,6 +128,12 @@ func AuthMiddleware() gin.HandlerFunc {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if store.IsExistAuthCache(token[7:]) {
|
||||||
|
if strings.HasPrefix(c.Request.URL.Path, "/1/me") {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if token[7:] != rootToken {
|
if token[7:] != rootToken {
|
||||||
u, err := store.GetUserByID(uint(1))
|
u, err := store.GetUserByID(uint(1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -178,7 +194,8 @@ func Handleinit(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func HandleMe(c *gin.Context) {
|
func HandleMe(c *gin.Context) {
|
||||||
u, err := store.GetUserByID(1)
|
token := c.GetHeader("Authorization")
|
||||||
|
u, err := store.GetUserByToken(token[7:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
@@ -196,6 +213,40 @@ func HandleMe(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, resJSON)
|
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) {
|
func HandleKeys(c *gin.Context) {
|
||||||
keys, err := store.GetAllKeys()
|
keys, err := store.GetAllKeys()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -237,7 +288,55 @@ func HandleAddKey(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
k := &store.Key{
|
k := &store.Key{
|
||||||
ApiType: "azure_openai",
|
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",
|
||||||
Name: body.Name,
|
Name: body.Name,
|
||||||
Key: body.Key,
|
Key: body.Key,
|
||||||
ResourceNmae: keynames[1],
|
ResourceNmae: keynames[1],
|
||||||
@@ -337,8 +436,12 @@ func HandleDelUser(c *gin.Context) {
|
|||||||
|
|
||||||
func HandleResetUserToken(c *gin.Context) {
|
func HandleResetUserToken(c *gin.Context) {
|
||||||
id := to.Int(c.Param("id"))
|
id := to.Int(c.Param("id"))
|
||||||
|
newtoken := c.Query("token")
|
||||||
|
if newtoken == "" {
|
||||||
|
newtoken = uuid.NewString()
|
||||||
|
}
|
||||||
|
|
||||||
if err := store.UpdateUser(uint(id), uuid.NewString()); err != nil {
|
if err := store.UpdateUser(uint(id), newtoken); err != nil {
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -382,6 +485,7 @@ func HandleProy(c *gin.Context) {
|
|||||||
chatreq = openai.ChatCompletionRequest{}
|
chatreq = openai.ChatCompletionRequest{}
|
||||||
chatres = openai.ChatCompletionResponse{}
|
chatres = openai.ChatCompletionResponse{}
|
||||||
chatlog store.Tokens
|
chatlog store.Tokens
|
||||||
|
onekey store.Key
|
||||||
pre_prompt string
|
pre_prompt string
|
||||||
req *http.Request
|
req *http.Request
|
||||||
err error
|
err error
|
||||||
@@ -390,6 +494,24 @@ func HandleProy(c *gin.Context) {
|
|||||||
auth := c.Request.Header.Get("Authorization")
|
auth := c.Request.Header.Get("Authorization")
|
||||||
if len(auth) > 7 && auth[:7] == "Bearer " {
|
if len(auth) > 7 && auth[:7] == "Bearer " {
|
||||||
localuser = store.IsExistAuthCache(auth[7:])
|
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 {
|
if c.Request.URL.Path == "/v1/chat/completions" && localuser {
|
||||||
@@ -399,30 +521,53 @@ func HandleProy(c *gin.Context) {
|
|||||||
}})
|
}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
onekey := store.FromKeyCacheRandomItemKey()
|
|
||||||
|
ChatHandler(c)
|
||||||
|
return
|
||||||
|
|
||||||
if err := c.BindJSON(&chatreq); err != nil {
|
if err := c.BindJSON(&chatreq); err != nil {
|
||||||
c.AbortWithError(http.StatusBadRequest, err)
|
c.AbortWithError(http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
chatlog.Model = chatreq.Model
|
chatlog.Model = chatreq.Model
|
||||||
for _, m := range chatreq.Messages {
|
for _, m := range chatreq.Messages {
|
||||||
pre_prompt += m.Content + "\n"
|
pre_prompt += m.Content + "\n"
|
||||||
}
|
}
|
||||||
chatlog.PromptHash = cryptor.Md5String(pre_prompt)
|
chatlog.PromptHash = cryptor.Md5String(pre_prompt)
|
||||||
chatlog.PromptCount = NumTokensFromMessages(chatreq.Messages, chatreq.Model)
|
chatlog.PromptCount = tokenizer.NumTokensFromMessages(chatreq.Messages, chatreq.Model)
|
||||||
isStream = chatreq.Stream
|
isStream = chatreq.Stream
|
||||||
chatlog.UserID, _ = store.GetUserID(auth[7:])
|
chatlog.UserID, _ = store.GetUserID(auth[7:])
|
||||||
|
|
||||||
var body bytes.Buffer
|
var body bytes.Buffer
|
||||||
json.NewEncoder(&body).Encode(chatreq)
|
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 请求
|
// 创建 API 请求
|
||||||
switch onekey.ApiType {
|
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":
|
case "azure_openai":
|
||||||
var buildurl string
|
var buildurl string
|
||||||
var apiVersion = "2023-05-15"
|
var apiVersion = "2023-05-15"
|
||||||
if onekey.EndPoint != "" {
|
if onekey.EndPoint != "" {
|
||||||
buildurl = fmt.Sprintf("https://%s/openai/deployments/%s/chat/completions?api-version=%s", onekey.EndPoint, modelmap(chatreq.Model), apiVersion)
|
buildurl = fmt.Sprintf("%s/openai/deployments/%s/chat/completions?api-version=%s", onekey.EndPoint, modelmap(chatreq.Model), apiVersion)
|
||||||
} else {
|
} else {
|
||||||
buildurl = fmt.Sprintf("https://%s.openai.azure.com/openai/deployments/%s/chat/completions?api-version=%s", onekey.ResourceNmae, modelmap(chatreq.Model), apiVersion)
|
buildurl = fmt.Sprintf("https://%s.openai.azure.com/openai/deployments/%s/chat/completions?api-version=%s", onekey.ResourceNmae, modelmap(chatreq.Model), apiVersion)
|
||||||
}
|
}
|
||||||
@@ -432,7 +577,12 @@ func HandleProy(c *gin.Context) {
|
|||||||
case "openai":
|
case "openai":
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
req, err = http.NewRequest(c.Request.Method, baseUrl+c.Request.RequestURI, &body)
|
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.Header = c.Request.Header
|
req.Header = c.Request.Header
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", onekey.Key))
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", onekey.Key))
|
||||||
}
|
}
|
||||||
@@ -446,7 +596,7 @@ func HandleProy(c *gin.Context) {
|
|||||||
req, err = http.NewRequest(c.Request.Method, baseUrl+c.Request.RequestURI, c.Request.Body)
|
req, err = http.NewRequest(c.Request.Method, baseUrl+c.Request.RequestURI, c.Request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.Header = c.Request.Header
|
req.Header = c.Request.Header
|
||||||
@@ -455,7 +605,7 @@ func HandleProy(c *gin.Context) {
|
|||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
@@ -487,41 +637,49 @@ func HandleProy(c *gin.Context) {
|
|||||||
reader := bufio.NewReader(resp.Body)
|
reader := bufio.NewReader(resp.Body)
|
||||||
|
|
||||||
if resp.StatusCode == 200 && localuser {
|
if resp.StatusCode == 200 && localuser {
|
||||||
if isStream {
|
switch onekey.ApiType {
|
||||||
contentCh := fetchResponseContent(c, reader)
|
case "claude":
|
||||||
var buffer bytes.Buffer
|
claude.TransRsp(c, isStream, chatlog, reader)
|
||||||
for content := range contentCh {
|
return
|
||||||
buffer.WriteString(content)
|
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
|
||||||
}
|
}
|
||||||
chatlog.CompletionCount = NumTokensFromStr(buffer.String(), chatreq.Model)
|
res, err := io.ReadAll(reader)
|
||||||
chatlog.TotalTokens = chatlog.PromptCount + chatlog.CompletionCount
|
if err != nil {
|
||||||
chatlog.Cost = fmt.Sprintf("%.6f", Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
|
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))
|
||||||
if err := store.Record(&chatlog); err != nil {
|
if err := store.Record(&chatlog); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
if err := store.SumDaily(chatlog.UserID); err != nil {
|
if err := store.SumDaily(chatlog.UserID); err != nil {
|
||||||
log.Println(err)
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -582,25 +740,26 @@ func HandleReverseProxy(c *gin.Context) {
|
|||||||
proxy.ServeHTTP(c.Writer, req)
|
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) {
|
func HandleUsage(c *gin.Context) {
|
||||||
fromStr := c.Query("from")
|
fromStr := c.Query("from")
|
||||||
toStr := c.Query("to")
|
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)
|
usage, err := store.QueryUsage(fromStr, toStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -659,58 +818,163 @@ func fetchResponseContent(ctx *gin.Context, responseBody *bufio.Reader) <-chan s
|
|||||||
return contentCh
|
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 {
|
func modelmap(in string) string {
|
||||||
switch in {
|
// gpt-3.5-turbo -> gpt-35-turbo
|
||||||
case "gpt-3.5-turbo":
|
if strings.Contains(in, ".") {
|
||||||
return "gpt-35-turbo"
|
return strings.ReplaceAll(in, ".", "")
|
||||||
case "gpt-4":
|
|
||||||
return "gpt-4"
|
|
||||||
}
|
}
|
||||||
return in
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Sakurasan/to"
|
"github.com/Sakurasan/to"
|
||||||
"github.com/patrickmn/go-cache"
|
"github.com/patrickmn/go-cache"
|
||||||
@@ -40,6 +42,29 @@ func FromKeyCacheRandomItemKey() Key {
|
|||||||
return item.Object.(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() {
|
func LoadAuthCache() {
|
||||||
AuthCache = cache.New(cache.NoExpiration, cache.NoExpiration)
|
AuthCache = cache.New(cache.NoExpiration, cache.NoExpiration)
|
||||||
users, err := GetAllUsers()
|
users, err := GetAllUsers()
|
||||||
|
|||||||
@@ -68,6 +68,21 @@ func QueryUsage(from, to string) ([]CalcUsage, error) {
|
|||||||
return results, nil
|
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 {
|
type Tokens struct {
|
||||||
UserID int
|
UserID int
|
||||||
PromptCount int
|
PromptCount int
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ package store
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
|
gorm.Model
|
||||||
IsDelete bool `gorm:"default:false" json:"IsDelete"`
|
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"`
|
Name string `gorm:"unique;not null" json:"name,omitempty"`
|
||||||
Token string `gorm:"unique;not null" json:"token,omitempty"`
|
Token string `gorm:"unique;not null" json:"token,omitempty"`
|
||||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||||
@@ -73,6 +76,15 @@ func GetUserByName(name string) (*User, error) {
|
|||||||
return &user, nil
|
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) {
|
func GetUserID(authkey string) (int, error) {
|
||||||
var user User
|
var user User
|
||||||
result := db.Where(&User{Token: authkey}).First(&user)
|
result := db.Where(&User{Token: authkey}).First(&user)
|
||||||
|
|||||||
Reference in New Issue
Block a user