Compare commits

..

2 Commits

Author SHA1 Message Date
1iaan
5ce2a5f1a3 compose 2026-04-10 12:00:03 +08:00
1iaan
94bdba930e frontend 兼容 2026-04-06 11:38:45 +08:00
206 changed files with 29047 additions and 34763 deletions

7
.env
View File

@@ -1,7 +0,0 @@
MOONSHOT_API_KEY=sk-8NMdsGbDAMpWdd6hrKHepr1tNVXTy2QppKAqJkoJcHd6TYLs
# frontend 对外端口
FRONTEND_PORT=1025
# ai-chat-service embedding 配置
AI_CHAT_EMBEDDING_API_KEY=d51b903546814cc9981d3649a4a899a3.NQOtz3ocRtQwimh9

View File

@@ -8,36 +8,12 @@ protoc \
```
```shell
# chatgpt-web-backend
go mod tidy
go run cmd/main.go \
--frontend-path www \
--openapi-key $MOONSHOT_API_KEY \
--openapi-base-url https://api.moonshot.cn/v1 \
--openai-model kimi-k2.5 \
--openai-temperature 100 \
--openai-presence-penalty 0 \
--openai-frequency-penalty 0
docker build -t chatgpt-web-backend:1.0.0 .
docker run -d --name chatgpt-web-backend \
-p 7080:7080 \
chatgpt-web-backend:1.0.0 \
/app/server \
--frontend-path www \
--openapi-key "$MOONSHOT_API_KEY" \
--openapi-base-url https://api.moonshot.cn/v1 \
--openai-model kimi-k2.5 \
--openai-temperature 100 \
--openai-presence-penalty 0 \
--openai-frequency-penalty 0
# ai-chat-web
# ai-chat-backend
docker build -t ai-chat-backend:1.0.0 .
# ai-chat-service
GOCACHE=/tmp/ai-chat-service-gocache go build -o ai-chat-service-bin ./chat-server
docker build -t ai-chat-service:1.0.0 .
# chatgpt-web-frontend

View File

@@ -7,7 +7,7 @@ log:
logPath: "runtime/logs/app.log"
chat:
# 使用的训练模型
model: "kimi-k2.5"
model: "kimi-k2-turbo-preview"
# 单次请求的上下文总长度,包括:请求消息+响应消息
max_tokens: 4096
# 表示语言模型输出的随机性和创造性

View File

@@ -6,7 +6,7 @@ log:
level: "info"
logPath: "runtime/logs/app.log"
chat:
model: "kimi-k2.5"
model: "kimi-k2-turbo-preview"
max_tokens: 4096
temperature: 1
top_p: 1

View File

@@ -74,7 +74,7 @@ func normalizeConfig(conf *Config) {
conf.FrontendPath = "www"
}
if conf.Chat.Model == "" {
conf.Chat.Model = "kimi-k2.5"
conf.Chat.Model = "kimi-k2-turbo-preview"
}
if conf.Chat.MaxTokens == 0 {
conf.Chat.MaxTokens = 4096

View File

@@ -0,0 +1,5 @@
.git
.gitignore
ai-chat-service-bin
runtime/logs
runtime/*.log

View File

@@ -1,11 +1,34 @@
FROM golang:1.25 AS builder
ENV GOPROXY=https://proxy.golang.com.cn,https://goproxy.cn,direct \
GOSUMDB=sum.golang.google.cn \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64
WORKDIR /src/ai-chat-service
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go build -o /out/ai-chat-service ./chat-server
FROM alpine:3.18
ADD ./grpc_health_probe-linux-amd64 /usr/bin/grpc_health_probe
RUN chmod +x /usr/bin/grpc_health_probe
LABEL maintainer="nick"
WORKDIR /app/
COPY ./ai-chat-service-bin ./ai-chat-service
COPY --from=builder /out/ai-chat-service ./ai-chat-service
COPY ./docker.config.yaml /app/config.yaml
# 指定入口程序
ENTRYPOINT ["./ai-chat-service"]
# 指定容器的启动命令或者入口程序的参数
CMD ["--config=config.yaml"]

View File

@@ -6,9 +6,9 @@ log:
level: "info"
logPath: "runtime/logs/app.log"
chat:
api_key: "sk-8NMdsGbDAMpWdd6hrKHepr1tNVXTy2QppKAqJkoJcHd6TYLs"
api_key: "xxxxxxxxxxx"
base_url: "https://api.moonshot.cn/v1"
model: "kimi-k2.5"
model: "kimi-k2-turbo-preview"
max_tokens: 4096
temperature: 1
top_p: 0.95

View File

@@ -2,6 +2,7 @@ package config
import (
"log"
"os"
"github.com/spf13/viper"
)
@@ -113,6 +114,7 @@ func InitConfig(filePath string, typ ...string) {
log.Fatal(err)
}
normalizeConfig(conf)
applySecretEnvOverrides(conf)
}
@@ -167,3 +169,15 @@ func normalizeConfig(conf *Config) {
conf.Embedding.Timeout = 10
}
}
func applySecretEnvOverrides(conf *Config) {
if v := os.Getenv("MOONSHOT_API_KEY"); v != "" {
conf.Chat.ApiKey = v
}
if v := os.Getenv("AI_CHAT_EMBEDDING_API_KEY"); v != "" {
conf.Embedding.ApiKey = v
}
if v := os.Getenv("REDIS_PASSWORD"); v != "" {
conf.Redis.Pwd = v
}
}

View File

@@ -7,8 +7,6 @@ services:
MYSQL_ROOT_PASSWORD: root
command:
- --default-authentication-plugin=mysql_native_password
# ports:
# - "3306:3306"
volumes:
- /data/mysql:/var/lib/mysql
- /home/lian/share/aichat/init/create_db.sql:/docker-entrypoint-initdb.d/create_db.sql:ro
@@ -33,20 +31,22 @@ services:
- /home/lian/share/aichat/init/pgvector-init.sql:/docker-entrypoint-initdb.d/pgvector-init.sql:ro
tokenizer:
build:
context: ../tokenizer
image: tokenizer:1.0.0
container_name: tokenizer
# 容器内端口3002
ports:
- "3002:3002"
restart: unless-stopped
sensitive-filter:
build:
context: ../keywords-filter
image: keywords-filter:1.0.0
container_name: sensitive-filter
# 容器内端口50053
volumes:
- /home/lian/share/aichat/keywords-filter/dev.config.yaml:/app/config.yaml:ro
- /home/lian/share/aichat/keywords-filter/dict.txt:/app/dict.txt:ro
- /home/lian/share/aichat/ai-chat-stack/configs/sensitive.yaml:/app/config.yaml:ro
- /home/lian/share/aichat/ai-chat-stack/configs/sensitive-dict.txt:/app/dict.txt:ro
command:
- --config=/app/config.yaml
- --dict=/app/dict.txt
@@ -55,12 +55,13 @@ services:
restart: unless-stopped
keywords-filter:
build:
context: ../keywords-filter
image: keywords-filter:1.0.0
container_name: keywords-filter
# 容器内端口50054
volumes:
- /home/lian/share/aichat/keywords-filter/dev.kw.config.yaml:/app/config.yaml:ro
- /home/lian/share/aichat/keywords-filter/keyword-dict.txt:/app/dict.txt:ro
- /home/lian/share/aichat/ai-chat-stack/configs/keywords.yaml:/app/config.yaml:ro
- /home/lian/share/aichat/ai-chat-stack/configs/keywords-dict.txt:/app/dict.txt:ro
command:
- --config=/app/config.yaml
- --dict=/app/dict.txt
@@ -70,11 +71,13 @@ services:
ai-chat-service:
build:
context: ./ai-chat-service
context: ../ai-chat-service
image: ai-chat-service:1.0.0
container_name: ai-chat-service
env_file:
- .env
volumes:
- /home/lian/share/aichat/ai-chat-service/docker.config.yaml:/app/config.yaml:ro
- /home/lian/share/aichat/ai-chat-stack/configs/ai-chat-service.yaml:/app/config.yaml:ro
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
@@ -92,24 +95,26 @@ services:
retries: 5
restart: unless-stopped
chatgpt-web-backend:
ai-chat-backend:
build:
context: ./ai-chat-backend
context: ../ai-chat-backend
image: ai-chat-backend:1.0.0
container_name: chatgpt-web-backend
# 容器内端口7080
container_name: ai-chat-backend
ports:
- "7080:7080"
volumes:
- /home/lian/share/aichat/ai-chat-backend/docker.config.yaml:/app/config.yaml:ro
- /home/lian/share/aichat/ai-chat-stack/configs/ai-chat-backend.yaml:/app/config.yaml:ro
depends_on:
- ai-chat-service
restart: unless-stopped
chatgpt-web-frontend:
image: chatgpt-web-frontend:1.0.0
container_name: chatgpt-web-frontend
ai-chat-web:
build:
context: ../ai-chat-web
image: ai-chat-web:1.0.0
container_name: ai-chat-web
depends_on:
- chatgpt-web-backend
# 容器内端口80
- ai-chat-backend
ports:
- "${FRONTEND_PORT}:80"
- "${FRONTEND_PORT:-1025}:80"
restart: unless-stopped

View File

@@ -0,0 +1,22 @@
http:
ip: 0.0.0.0
port: 7080
frontend_path: "www"
log:
level: "info"
logPath: "runtime/logs/app.log"
chat:
model: "kimi-k2-turbo-preview"
max_tokens: 4096
temperature: 1
top_p: 1
presence_penalty: 0
frequency_penalty: 0
bot_desc: "你是一个AI助手我需要你模拟一名资深的软件工程师来回答我的问题"
min_response_tokens: 600
context_ttl: 1800
context_len: 4
dependOn:
ai-chat-service:
address: "ai-chat-service:50055"
accessToken: "me256487ang1chubdpdialoud22sev1ozhoguumyqca"

View File

@@ -0,0 +1,54 @@
server:
ip: 0.0.0.0
port: 50055
accessToken: "me256487ang1chubdpdialoud22sev1ozhoguumyqca"
log:
level: "info"
logPath: "runtime/logs/app.log"
chat:
api_key: "__SET_FROM_ENV__"
base_url: "https://api.moonshot.cn/v1"
model: "kimi-k2-turbo-preview" # kimi-k2.5
max_tokens: 4096
temperature: 1
top_p: 0.95
presence_penalty: 0
frequency_penalty: 0
bot_desc: "你是一个AI助手我需要你模拟一名资深的软件工程师来回答我的问题"
min_response_tokens: 600
context_ttl: 1800
context_len: 4
redis:
host: "host.docker.internal"
port: 8888
pwd: "123456"
mysql:
dsn: "root:root@tcp(mysql:3306)/ai_chat?collation=utf8mb4_unicode_ci&charset=utf8mb4"
maxLifeTime: 3600
maxOpenConn: 10
maxIdleConn: 10
dependOn:
sensitive:
address: "sensitive-filter:50053"
accessToken: "ang1chubdev1ozhome256487d22sapguuv1ozhom"
keywords:
address: "keywords-filter:50054"
accessToken: "ang1chubdev1ozhome256487d22sapguuv1ozhom"
tokenizer:
address: "http://tokenizer:3002"
vector:
provider: "pgvector"
threshold: 0.99
pgvector:
dsn: "postgres://postgres:postgres@pgvector:5432/ai_chat?sslmode=disable"
table: "chat_record_vectors"
dimensions: 1024
maxLifeTime: 3600
maxOpenConn: 10
maxIdleConn: 10
embedding:
provider: "openai-compatible"
base_url: "https://open.bigmodel.cn/api/paas/v4"
api_key: "__SET_FROM_ENV__"
model: "embedding-2"
timeout: 10

View File

@@ -0,0 +1,192 @@
golang
defer
recover
sync
Protobuf
gin
grpc-gateway
OpenTelemetry
OTel
otel
k8s
Kubernetes
kubernetes
Docker
docker
Istio
istio
Prometheus
prometheus
cadvisor
cAdvisor
Elastic
Kibana
Grafana
apiserver
CI/CD
ci/cd
ArgoCD
argo
Argo
kaniko
Mesh
Volume
volume
promQL
PromQL
kafka
ingress
StorageClass
VolumeClaim
gitlab
openflow
dpdk
vpp
ovs
spdk
virtio
vhost
qemu
vSwitch
bridge
hugepage
nvme
dpvs
iperf3
rfc2544
ioengine
PCI
vxlan
gre
kni
Kernel
内核
KernelThread
内核线程
Virtual
memory
虚拟内存
内存屏障
内存管理
Scheduler
调度器
File
文件系统
Device
driver
设备驱动程序
Syscall
系统调用
Process
scheduling
进程调度
Page
页表
Swap
交换空间
Mount
Inode
挂载
索引节点
Block
块设备
Character
字符设备
IRQ
Kconfig
内核配置
Perf
Ftrace
内核跟踪工具
Valgrind
内存调试工具
System
系统定时器
DMA
伙伴系统
信号与槽
Signals
Slots
事件处理程序
Event
QML
多线程编程
Multithreading
Programming
QThread
QtQuick
模型
视图架构
Model/View
QObject
QWidget
QRegularExpression
QDesktopWidget
QNetworkAccessManager
QTcpServer
QTcpSocket
QUdpSocket
QMutex
SQLite/MySQL
MySQL编程
SQLite编程
OpenCV
OpenGL
Qt数据库编程
Qt网络编程
Linux
tcp
redis
mysql
网络
nginx
协程
io_uring
内存泄漏
bpf
ebpf
skynet
openresty
RocksDB
TiDB
ceph
etcd
fuse
p2p
http
mqtt
cuda
mutex
spinlock
hash
rbtree
btree
Makefile
git
wrk
Cuda
CUDA
D3D
d3d
ffmpeg
RTSP
WebRTC
PCM
RGB
YUv
MP4
FLV
TS
VLC
EasylCE
flvAnalyser
mp4box
audacity
Elecard
AAC
h264
SDL
AVFormat
AVCodec
AVPacket

View File

@@ -0,0 +1,7 @@
server:
ip: 0.0.0.0
port: 50054
accessToken: "ang1chubdev1ozhome256487d22sapguuv1ozhom"
log:
level: "info"
logPath: "runtime/logs/app.log"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
server:
ip: 0.0.0.0
port: 50053
accessToken: "ang1chubdev1ozhome256487d22sapguuv1ozhom"
log:
level: "info"
logPath: "runtime/logs/app.log"

View File

@@ -5,3 +5,4 @@ Dockerfile
.*
*/.*
!.env
!.env.production

12
ai-chat-web/.env.develop Normal file
View File

@@ -0,0 +1,12 @@
# Glob API URL
VITE_GLOB_API_URL=/api
VITE_APP_API_BASE_URL=http://localhost:7080/
# Whether long replies are supported, which may result in higher API fees
VITE_GLOB_OPEN_LONG_REPLY=false
# When you want to use PWA
VITE_GLOB_APP_PWA=false
VITE_USER_CENTER="http://localhost:8082?sys=ai"

View File

@@ -1,10 +1,8 @@
# Glob API URL
VITE_GLOB_API_URL=/api
VITE_APP_API_BASE_URL=http://127.0.0.1:7080/
# Whether long replies are supported, which may result in higher API fees
VITE_GLOB_OPEN_LONG_REPLY=false
# When you want to use PWA
VITE_GLOB_APP_PWA=false
VITE_USER_CENTER="https://user.0voice.com?sys=ai"

View File

@@ -0,0 +1,41 @@
name: build_docker
on:
push:
branches: [main]
release:
types: [created] # 表示在创建新的 Release 时触发
jobs:
build_docker:
name: Build docker
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- run: |
echo "本次构建的版本为:${GITHUB_REF_NAME} (但是这个变量目前上下文中无法获取到)"
echo 本次构建的版本为:${{ github.ref_name }}
env
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v4
with:
context: .
push: true
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/chatgpt-web:${{ github.ref_name }}
${{ secrets.DOCKERHUB_USERNAME }}/chatgpt-web:latest

47
ai-chat-web/.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set node
uses: actions/setup-node@v3
with:
node-version: 18.x
- name: Setup
run: npm i -g @antfu/ni
- name: Install
run: nci
- name: Lint
run: nr lint:fix
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set node
uses: actions/setup-node@v3
with:
node-version: 18.x
- name: Setup
run: npm i -g @antfu/ni
- name: Install
run: nci
- name: Typecheck
run: nr type-check

View File

@@ -0,0 +1,59 @@
variables:
DEPLOY_ENV: ''
DEPLOY_IMG: ''
REPO: 'chatgpt-frontend'
SERVICE_NAME: 'chatgpt-stack_chatgpt-frontend'
workflow:
rules:
- if: $CI_COMMIT_BRANCH == "dev" && $CI_PIPELINE_SOURCE == "push"
variables:
DEPLOY_ENV: 'dev'
DEPLOY_IMG: "${REPO}:${CI_COMMIT_SHORT_SHA}"
- if: $CI_COMMIT_TAG
variables:
DEPLOY_ENV: 'prod'
DEPLOY_IMG: "${REPO}:${CI_COMMIT_TAG}"
- when: never
stages:
# 编译阶段
- build
# 部署阶段(部署到测试环境/部署到生产环境)
- deploy
build-job:
stage: build
tags:
- builder
before_script:
- docker login -u ${DOCKER_REGISTRY_USER} -p ${DOCKER_REGISTRY_PWD} ${DOCKER_REGISTRY}
script:
- docker build -t ${DOCKER_REGISTRY}/${DEPLOY_IMG} .
- docker push ${DOCKER_REGISTRY}/${DEPLOY_IMG}
deploy-dev-job:
only:
variables:
- $DEPLOY_ENV == "dev"
stage: deploy
tags:
- deployer
variables:
CONF_RM_STR: ""
before_script:
- docker login -u ${DOCKER_REGISTRY_USER} -p ${DOCKER_REGISTRY_PWD} ${DOCKER_REGISTRY}
script:
- docker service update ${SERVICE_NAME} --image ${DOCKER_REGISTRY}/${DEPLOY_IMG}
deploy-prod-job:
only:
variables:
- $DEPLOY_ENV == "prod"
stage: deploy
tags:
- deployer
variables:
CONF_RM_STR: ""
before_script:
- docker login -u ${DOCKER_REGISTRY_USER} -p ${DOCKER_REGISTRY_PWD} ${DOCKER_REGISTRY}
script:
- docker service update ${SERVICE_NAME} --image ${DOCKER_REGISTRY}/${DEPLOY_IMG}

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no -- commitlint --edit

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged

3
ai-chat-web/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar", "dbaeumer.vscode-eslint"]
}

65
ai-chat-web/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,65 @@
{
"prettier.enable": false,
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"json",
"jsonc",
"json5",
"yaml",
"yml",
"markdown"
],
"cSpell.words": [
"antfu",
"axios",
"bumpp",
"chatgpt",
"chenzhaoyu",
"commitlint",
"davinci",
"dockerhub",
"esno",
"GPTAPI",
"highlightjs",
"hljs",
"iconify",
"katex",
"katexmath",
"linkify",
"logprobs",
"mdhljs",
"mila",
"nodata",
"OPENAI",
"pinia",
"Popconfirm",
"rushstack",
"Sider",
"tailwindcss",
"traptitech",
"tsup",
"Typecheck",
"unplugin",
"VITE",
"vueuse",
"Zhao"
],
"i18n-ally.enabledParsers": [
"ts"
],
"i18n-ally.sortKeys": true,
"i18n-ally.keepFulfilled": true,
"i18n-ally.localesPaths": [
"src/locales"
],
"i18n-ally.keystyle": "nested"
}

View File

@@ -1,57 +1,3 @@
## v2.11.0
`2023-04-26`
> [chatgpt-web-plus](https://github.com/Chanzhaoyu/chatgpt-web-plus) 新界面、完整用户管理
## Enhancement
- 更新默认 `accessToken` 反代地址为 [[pengzhile](https://github.com/pengzhile)] 的 `https://ai.fakeopen.com/api/conversation` [[24min](https://github.com/Chanzhaoyu/chatgpt-web/pull/1567/files)]
- 添加自定义 `temperature``top_p` [[quzard](https://github.com/Chanzhaoyu/chatgpt-web/pull/1260)]
- 优化代码 [[shunyue1320](https://github.com/Chanzhaoyu/chatgpt-web/pull/1328)]
- 优化复制代码反馈效果
## BugFix
- 修复余额查询和文案 [[luckywangxi](https://github.com/Chanzhaoyu/chatgpt-web/pull/1174)][[zuoning777](https://github.com/Chanzhaoyu/chatgpt-web/pull/1296)]
- 修复默认语言错误 [[idawnwon](https://github.com/Chanzhaoyu/chatgpt-web/pull/1352)]
- 修复 `onRegenerate` 下问题 [[leafsummer](https://github.com/Chanzhaoyu/chatgpt-web/pull/1188)]
## Other
- 引导用户触发提示词 [[RyanXinOne](https://github.com/Chanzhaoyu/chatgpt-web/pull/1183)]
- 添加韩语翻译 [[Kamilake](https://github.com/Chanzhaoyu/chatgpt-web/pull/1372)]
- 添加俄语翻译 [[aquaratixc](https://github.com/Chanzhaoyu/chatgpt-web/pull/1571)]
- 优化翻译和文本检查 [[PeterDaveHello](https://github.com/Chanzhaoyu/chatgpt-web/pull/1460)]
- 移除无用文件
## v2.10.9
`2023-04-03`
> 更新默认 `accessToken` 反代地址为 [[pengzhile](https://github.com/pengzhile)] 的 `https://ai.fakeopen.com/api/conversation`
## Enhancement
- 添加 `socks5` 代理认证 [[yimiaoxiehou](https://github.com/Chanzhaoyu/chatgpt-web/pull/999)]
- 添加 `socks` 代理用户名密码的配置 [[hank-cp](https://github.com/Chanzhaoyu/chatgpt-web/pull/890)]
- 添加可选日志打印 [[zcong1993](https://github.com/Chanzhaoyu/chatgpt-web/pull/1041)]
- 更新侧边栏按钮本地化[[simonwu53](https://github.com/Chanzhaoyu/chatgpt-web/pull/911)]
- 优化代码块滚动条高度 [[Fog3211](https://github.com/Chanzhaoyu/chatgpt-web/pull/1153)]
## BugFix
- 修复 `PWA` 问题 [[bingo235](https://github.com/Chanzhaoyu/chatgpt-web/pull/807)]
- 修复 `ESM` 错误 [[kidonng](https://github.com/Chanzhaoyu/chatgpt-web/pull/826)]
- 修复反向代理开启时限流失效的问题 [[gitgitgogogo](https://github.com/Chanzhaoyu/chatgpt-web/pull/863)]
- 修复 `docker` 构建时 `.env` 可能被忽略的问题 [[zaiMoe](https://github.com/Chanzhaoyu/chatgpt-web/pull/877)]
- 修复导出异常错误 [[KingTwinkle](https://github.com/Chanzhaoyu/chatgpt-web/pull/938)]
- 修复空值异常 [[vchenpeng](https://github.com/Chanzhaoyu/chatgpt-web/pull/1103)]
- 移动端上的体验问题
## Other
- `Docker` 容器名字名义 [[LOVECHEN](https://github.com/Chanzhaoyu/chatgpt-web/pull/1035)]
- `kubernetes` 部署配置 [[CaoYunzhou](https://github.com/Chanzhaoyu/chatgpt-web/pull/1001)]
- 感谢 [[assassinliujie](https://github.com/Chanzhaoyu/chatgpt-web/pull/962)] 和 [[puppywang](https://github.com/Chanzhaoyu/chatgpt-web/pull/1017)] 的某些贡献
- 更新 `kubernetes/deploy.yaml` [[idawnwon](https://github.com/Chanzhaoyu/chatgpt-web/pull/1085)]
- 文档更新 [[#yi-ge](https://github.com/Chanzhaoyu/chatgpt-web/pull/883)]
- 文档更新 [[weifeng12x](https://github.com/Chanzhaoyu/chatgpt-web/pull/880)]
- 依赖更新
## v2.10.8
`2023-03-23`
@@ -125,7 +71,7 @@
`2023-03-13`
更新依赖,`access_token` 默认代理为 [pengzhile](https://github.com/pengzhile) 的 `https://bypass.duti.tech/api/conversation`
更新依赖,`access_token` 默认代理为 [acheong08](https://github.com/acheong08) 的 `https://bypass.duti.tech/api/conversation`
## Feature
- `Prompt` 商店在线导入可以导入两种 `recommend.json`里提到的模板 [simonwu53](https://github.com/Chanzhaoyu/chatgpt-web/pull/521)

15
ai-chat-web/Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
FROM quay.io/0voice/node:lts-alpine AS frontend
RUN npm install pnpm -g
WORKDIR /app
COPY package.json pnpm-lock.yaml .npmrc ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build-only
FROM quay.io/0voice/nginx:1.25.4 AS web
COPY ./docker/nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=frontend /app/dist/ /usr/share/nginx/html/
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -0,0 +1,64 @@
# ai-chat-web
## node 安装
### windows
1. 上[官网](https://nodejs.org/en)下载18.16.0 LTS版本
2. 查看node 是否安装成功
```
node -v
npm -v
```
3. 安装pnpm
```
npm install pnpm -g
```
### ubuntu
1. 设置 apt 源,设置后可查看/etc/apt/sources.list.d/nodesource.list 文件
```
curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
```
2. 安装nodejs
```
sudo apt-get install -y nodejs
```
3. 验证
```
node -v
npm -v
```
4. 安装pnpm
```
sudo npm install pnpm -g
```
## 编译运行
1. 依赖安装
```
pnpm bootstrap
```
2. 本地运行
```
pnpm dev
```
3. 打包发布版本
```
pnpm build-only
```
## 提交代码的规则
```
* commitlint 规则是指在提交代码时要遵循的规范,常见的 commitlint 规则如下:
* type用于说明 commit 的类型,例如 feat新功能、fix修复 bug、docs文档更新、style样式修改、refactor重构代码等。
* scope用于说明 commit 影响的范围,例如组件、模块、页面等。
* subject用于简短地描述 commit 的内容,建议不超过 50 个字符。
* body用于详细描述 commit 的改动内容,可以分成多行。
* footer用于关闭 issue 或者添加相关链接等信息。
* 长度限制commit message 不应该过长,一般不超过 72 个字符。
```

View File

@@ -0,0 +1 @@
export * from './proxy'

View File

@@ -0,0 +1,16 @@
import type { ProxyOptions } from 'vite'
export function createViteProxy(isOpenProxy: boolean, viteEnv: ImportMetaEnv) {
if (!isOpenProxy)
return
const proxy: Record<string, string | ProxyOptions> = {
'/api': {
target: viteEnv.VITE_APP_API_BASE_URL,
changeOrigin: true,
rewrite: path => path.replace('/api/', '/'),
},
}
return proxy
}

View File

@@ -2,39 +2,33 @@ version: '3'
services:
app:
container_name: chatgpt-web
image: chenzhaoyu94/chatgpt-web # 总是使用latest,更新时重新pull该tag镜像即可
ports:
- 3002:3002
environment:
# 二选一
OPENAI_API_KEY:
OPENAI_API_KEY: sk-xxx
# 二选一
OPENAI_ACCESS_TOKEN:
OPENAI_ACCESS_TOKEN: xxx
# API接口地址可选设置 OPENAI_API_KEY 时可用
OPENAI_API_BASE_URL:
OPENAI_API_BASE_URL: xxx
# API模型可选设置 OPENAI_API_KEY 时可用
OPENAI_API_MODEL:
OPENAI_API_MODEL: xxx
# 反向代理,可选
API_REVERSE_PROXY:
API_REVERSE_PROXY: xxx
# 访问权限密钥,可选
AUTH_SECRET_KEY:
AUTH_SECRET_KEY: xxx
# 每小时最大请求次数,可选,默认无限
MAX_REQUEST_PER_HOUR: 0
# 超时,单位毫秒,可选
TIMEOUT_MS: 60000
# Socks代理可选和 SOCKS_PROXY_PORT 一起时生效
SOCKS_PROXY_HOST:
SOCKS_PROXY_HOST: xxx
# Socks代理端口可选和 SOCKS_PROXY_HOST 一起时生效
SOCKS_PROXY_PORT:
# Socks代理用户名可选和 SOCKS_PROXY_HOST & SOCKS_PROXY_PORT 一起时生效
SOCKS_PROXY_USERNAME:
# Socks代理密码可选和 SOCKS_PROXY_HOST & SOCKS_PROXY_PORT 一起时生效
SOCKS_PROXY_PASSWORD:
SOCKS_PROXY_PORT: xxx
# HTTPS_PROXY 代理,可选
HTTPS_PROXY:
HTTPS_PROXY: http://xxx:7890
nginx:
container_name: nginx
image: nginx:alpine
ports:
- '80:80'

View File

@@ -3,13 +3,6 @@ server {
server_name localhost;
charset utf-8;
error_page 500 502 503 504 /50x.html;
# 防止爬虫抓取
if ($http_user_agent ~* "360Spider|JikeSpider|Spider|spider|bot|Bot|2345Explorer|curl|wget|webZIP|qihoobot|Baiduspider|Googlebot|Googlebot-Mobile|Googlebot-Image|Mediapartners-Google|Adsbot-Google|Feedfetcher-Google|Yahoo! Slurp|Yahoo! Slurp China|YoudaoBot|Sosospider|Sogou spider|Sogou web spider|MSNBot|ia_archiver|Tomato Bot|NSPlayer|bingbot")
{
return 403;
}
location / {
root /usr/share/nginx/html;
try_files $uri /index.html;

View File

@@ -15,6 +15,6 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://chatgpt-web-backend:7080/api/;
proxy_pass http://ai-chat-backend:7080/api/;
}
}

View File

@@ -2,12 +2,12 @@
<html lang="zh-cmn-Hans">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="icon" type="image/svg+xml" href="/favicon.jpg">
<meta content="yes" name="apple-mobile-web-app-capable"/>
<link rel="apple-touch-icon" href="/favicon.ico">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
<title>ChatGPT Web</title>
<title>零声教学AI助手公测</title>
</head>
<body class="dark:bg-black">
@@ -79,5 +79,14 @@
</div>
<script type="module" src="/src/main.ts"></script>
</body>
<script>
var _hmt = _hmt || [];
(function () {
const hm = document.createElement('script')
hm.src = 'https://hm.baidu.com/hm.js?29783f1f3a946661c3d41e96752536d6'
const s = document.getElementsByTagName('script')[0]
s.parentNode.insertBefore(hm, s)
})()
</script>
</html>

View File

@@ -1,8 +1,8 @@
{
"name": "chatgpt-web",
"version": "2.11.0",
"version": "2.10.8",
"private": false,
"description": "ChatGPT Web",
"description": "零声教学AI助手",
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
"keywords": [
"chatgpt-web",
@@ -11,10 +11,10 @@
"vue"
],
"scripts": {
"dev": "vite",
"dev": "vite --mode develop",
"build": "run-p type-check build-only",
"preview": "vite preview",
"build-only": "vite build",
"build-only": "vite build --mode production",
"type-check": "vue-tsc --noEmit",
"lint": "eslint .",
"lint:fix": "eslint . --fix",

6903
ai-chat-web/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1,6 +1,6 @@
import type { AxiosProgressEvent, GenericAbortSignal } from 'axios'
import { post } from '@/utils/request'
import { useAuthStore, useSettingStore } from '@/store'
import { useSettingStore } from '@/store'
export function fetchChatAPI<T = any>(
prompt: string,
@@ -28,25 +28,10 @@ export function fetchChatAPIProcess<T = any>(
onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void },
) {
const settingStore = useSettingStore()
const authStore = useAuthStore()
let data: Record<string, any> = {
prompt: params.prompt,
options: params.options,
}
if (authStore.isChatGPTAPI) {
data = {
...data,
systemMessage: settingStore.systemMessage,
temperature: settingStore.temperature,
top_p: settingStore.top_p,
}
}
return post<T>({
url: '/chat-process',
data,
data: { prompt: params.prompt, options: params.options, systemMessage: settingStore.systemMessage },
signal: params.signal,
onDownloadProgress: params.onDownloadProgress,
})
@@ -64,3 +49,17 @@ export function fetchVerify<T>(token: string) {
data: { token },
})
}
export function fetchCode<T>(phone: string) {
return post<T>({
url: '/v1/sms/send/code',
data: { phone },
})
}
export function login<T>(phone: string, code: string) {
return post<T>({
url: '/v1/user/login',
data: { user_name: phone, pwd: code, type: 1 },
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -147,7 +147,7 @@ const clearPromptTemplate = () => {
message.success(t('common.clearSuccess'))
}
const importPromptTemplate = (from = 'online') => {
const importPromptTemplate = () => {
try {
const jsonData = JSON.parse(tempPromptValue.value)
let key = ''
@@ -168,7 +168,7 @@ const importPromptTemplate = (from = 'online') => {
}
for (const i of jsonData) {
if (!(key in i) || !(value in i))
if (!('key' in i) || !('value' in i))
throw new Error(t('store.importError'))
let safe = true
for (const j of promptList.value) {
@@ -191,8 +191,6 @@ const importPromptTemplate = (from = 'online') => {
catch {
message.error('JSON 格式错误,请检查 JSON 格式')
}
if (from === 'local')
showModal.value = !showModal.value
}
//
@@ -471,7 +469,7 @@ const dataSource = computed(() => {
block
type="primary"
:disabled="inputStatus"
@click="() => { importPromptTemplate('local') }"
@click="() => { importPromptTemplate() }"
>
{{ t('common.import') }}
</NButton>

View File

@@ -11,7 +11,7 @@ interface ConfigState {
apiModel?: string
socksProxy?: string
httpsProxy?: string
usage?: string
balance?: string
}
const authStore = useAuthStore()
@@ -52,17 +52,17 @@ onMounted(() => {
href="https://github.com/Chanzhaoyu/chatgpt-web"
target="_blank"
>
GitHub
Github
</a>
免费且基于 MIT 协议没有任何形式的付费行为
</p>
<p>
如果你觉得此项目对你有帮助请在 GitHub 帮我点个 Star 或者给予一点赞助谢谢
如果你觉得此项目对你有帮助请在 Github 帮我点个 Star 或者给予一点赞助谢谢
</p>
</div>
<p>{{ $t("setting.api") }}{{ config?.apiModel ?? '-' }}</p>
<p v-if="isChatGPTAPI">
{{ $t("setting.monthlyUsage") }}{{ config?.usage ?? '-' }}
{{ $t("setting.balance") }}{{ config?.balance ?? '-' }}
</p>
<p v-if="!isChatGPTAPI">
{{ $t("setting.reverseProxy") }}{{ config?.reverseProxy ?? '-' }}

View File

@@ -0,0 +1,46 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { NButton, NInput, useMessage } from 'naive-ui'
import { useSettingStore } from '@/store'
import type { SettingsState } from '@/store/modules/settings/helper'
import { t } from '@/locales'
const settingStore = useSettingStore()
const ms = useMessage()
const systemMessage = ref(settingStore.systemMessage ?? '')
function updateSettings(options: Partial<SettingsState>) {
settingStore.updateSetting(options)
ms.success(t('common.success'))
}
function handleReset() {
settingStore.resetSetting()
ms.success(t('common.success'))
window.location.reload()
}
</script>
<template>
<div class="p-4 space-y-5 min-h-[200px]">
<div class="space-y-6">
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.role') }}</span>
<div class="flex-1">
<NInput v-model:value="systemMessage" placeholder="" />
</div>
<NButton size="tiny" text type="primary" @click="updateSettings({ systemMessage })">
{{ $t('common.save') }}
</NButton>
</div>
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">&nbsp;</span>
<NButton size="small" @click="handleReset">
{{ $t('common.reset') }}
</NButton>
</div>
</div>
</div>
</template>

View File

@@ -57,8 +57,6 @@ const languageOptions: { label: string; key: Language; value: Language }[] = [
{ label: '简体中文', key: 'zh-CN', value: 'zh-CN' },
{ label: '繁體中文', key: 'zh-TW', value: 'zh-TW' },
{ label: 'English', key: 'en-US', value: 'en-US' },
{ label: '한국어', key: 'ko-KR', value: 'ko-KR' },
{ label: 'Русский язык', key: 'ru-RU', value: 'ru-RU' },
]
function updateUserInfo(options: Partial<UserInfo>) {

View File

@@ -1,13 +1,12 @@
<script setup lang='ts'>
import { computed, useAttrs } from 'vue'
import { Icon } from '@iconify/vue'
import type { IconifyIcon } from '@iconify/vue'
interface Props {
icon?: string | IconifyIcon
icon?: string
}
const props = defineProps<Props>()
defineProps<Props>()
const attrs = useAttrs()
@@ -18,5 +17,5 @@ const bindAttrs = computed<{ class: string; style: string }>(() => ({
</script>
<template>
<Icon :icon="props.icon ?? ''" v-bind="bindAttrs" />
<Icon :icon="icon" v-bind="bindAttrs" />
</template>

View File

@@ -1,5 +1,5 @@
import { computed } from 'vue'
import { enUS, koKR, zhCN, zhTW } from 'naive-ui'
import { enUS, zhCN, zhTW } from 'naive-ui'
import { useAppStore } from '@/store'
import { setLocale } from '@/locales'
@@ -11,12 +11,6 @@ export function useLanguage() {
case 'en-US':
setLocale('en-US')
return enUS
case 'ru-RU':
setLocale('ru-RU')
return enUS
case 'ko-KR':
setLocale('ko-KR')
return koKR
case 'zh-CN':
setLocale('zh-CN')
return zhCN
@@ -25,7 +19,7 @@ export function useLanguage() {
return zhTW
default:
setLocale('zh-CN')
return zhCN
return enUS
}
})

View File

@@ -9,10 +9,11 @@ export function useTheme() {
const OsTheme = useOsTheme()
const isDark = computed(() => {
if (appStore.theme === 'auto')
return OsTheme.value === 'dark'
else
return appStore.theme === 'dark'
return true
// if (appStore.theme === 'auto')
// return OsTheme.value === 'dark'
// else
// return appStore.theme === 'dark'
})
const theme = computed(() => {

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -28,8 +28,7 @@ export default {
unauthorizedTips: 'Unauthorized, please verify first.',
},
chat: {
newChatButton: 'New Chat',
placeholder: 'Ask me anything...(Shift + Enter = line break, "/" to trigger prompts)',
placeholder: 'Ask me anything...(Shift + Enter = line break)',
placeholderMobile: 'Ask me anything...',
copy: 'Copy',
copied: 'Copied',
@@ -59,8 +58,6 @@ export default {
name: 'Name',
description: 'Description',
role: 'Role',
temperature: 'Temperature',
top_p: 'Top_p',
resetUserInfo: 'Reset UserInfo',
chatHistory: 'ChatHistory',
theme: 'Theme',
@@ -71,10 +68,8 @@ export default {
socks: 'Socks',
httpsProxy: 'HTTPS Proxy',
balance: 'API Balance',
monthlyUsage: 'Monthly Usage',
},
store: {
siderButton: 'Prompt Store',
local: 'Local',
online: 'Online',
title: 'Title',

View File

@@ -1,10 +1,8 @@
import type { App } from 'vue'
import { createI18n } from 'vue-i18n'
import enUS from './en-US'
import koKR from './ko-KR'
import zhCN from './zh-CN'
import zhTW from './zh-TW'
import ruRU from './ru-RU'
import { useAppStoreWithOut } from '@/store/modules/app'
import type { Language } from '@/store/modules/app/helper'
@@ -18,10 +16,8 @@ const i18n = createI18n({
allowComposition: true,
messages: {
'en-US': enUS,
'ko-KR': koKR,
'zh-CN': zhCN,
'zh-TW': zhTW,
'ru-RU': ruRU,
},
})

View File

@@ -24,12 +24,11 @@ export default {
wrong: '好像出错了,请稍后再试。',
success: '操作成功',
failed: '操作失败',
verify: '验证',
verify: '登录',
unauthorizedTips: '未经授权,请先进行验证。',
},
chat: {
newChatButton: '新建聊天',
placeholder: '来说点什么吧...Shift + Enter = 换行,"/" 触发提示词)',
placeholder: '来说点什么吧...Shift + Enter = 换行)',
placeholderMobile: '来说点什么...',
copy: '复制',
copied: '复制成功',
@@ -59,8 +58,6 @@ export default {
name: '名称',
description: '描述',
role: '角色设定',
temperature: 'Temperature',
top_p: 'Top_p',
resetUserInfo: '重置用户信息',
chatHistory: '聊天记录',
theme: '主题',
@@ -71,10 +68,8 @@ export default {
socks: 'Socks',
httpsProxy: 'HTTPS Proxy',
balance: 'API余额',
monthlyUsage: '本月使用量',
},
store: {
siderButton: '提示词商店',
local: '本地',
online: '在线',
title: '标题',

View File

@@ -28,8 +28,7 @@ export default {
unauthorizedTips: '未經授權,請先進行驗證。',
},
chat: {
newChatButton: '新增對話',
placeholder: '來說點什麼...Shift + Enter = 換行,"/" 觸發提示詞)',
placeholder: '來說點什麼...Shift + Enter = 換行)',
placeholderMobile: '來說點什麼...',
copy: '複製',
copied: '複製成功',
@@ -53,14 +52,12 @@ export default {
setting: {
setting: '設定',
general: '總覽',
advanced: '進階',
advanced: '高級',
config: '設定',
avatarLink: '頭貼連結',
name: '名稱',
description: '描述',
role: '角色設定',
temperature: 'Temperature',
top_p: 'Top_p',
resetUserInfo: '重設使用者資訊',
chatHistory: '紀錄',
theme: '主題',
@@ -70,11 +67,9 @@ export default {
timeout: '逾時',
socks: 'Socks',
httpsProxy: 'HTTPS Proxy',
balance: 'API Credit 餘額',
monthlyUsage: '本月使用量',
balance: 'API額',
},
store: {
siderButton: '提示詞商店',
local: '本機',
online: '線上',
title: '標題',

View File

@@ -0,0 +1,30 @@
import type { Router } from 'vue-router'
import { useAuthStoreWithout } from '@/store/modules/auth'
export function setupPageGuard(router: Router) {
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStoreWithout()
if (!authStore.session) {
// try {
// const data = await authStore.getSession()
// if (String(data.auth) === 'false' && authStore.token)
// authStore.removeToken()
// if (to.path === '/500')
// next({ name: 'Root' })
// else
// next()
// }
// catch (error) {
// if (to.path !== '/500')
// next({ name: '500' })
// else
// next()
// }
next()
}
else {
next()
}
})
}

View File

@@ -4,7 +4,7 @@ const LOCAL_NAME = 'appSetting'
export type Theme = 'light' | 'dark' | 'auto'
export type Language = 'zh-CN' | 'zh-TW' | 'en-US' | 'ko-KR' | 'ru-RU'
export type Language = 'zh-CN' | 'zh-TW' | 'en-US'
export interface AppState {
siderCollapsed: boolean

View File

@@ -2,12 +2,7 @@ import { ss } from '@/utils/storage'
const LOCAL_NAME = 'promptStore'
export interface PromptItem {
key: string
value: string
}
export type PromptList = PromptItem[]
export type PromptList = []
export interface PromptStore {
promptList: PromptList

View File

@@ -1,12 +1,12 @@
import { defineStore } from 'pinia'
import type { PromptList, PromptStore } from './helper'
import type { PromptStore } from './helper'
import { getLocalPromptList, setLocalPromptList } from './helper'
export const usePromptStore = defineStore('prompt-store', {
state: (): PromptStore => getLocalPromptList(),
actions: {
updatePromptList(promptList: PromptList) {
updatePromptList(promptList: []) {
this.$patch({ promptList })
setLocalPromptList({ promptList })
},

View File

@@ -4,15 +4,12 @@ const LOCAL_NAME = 'settingsStorage'
export interface SettingsState {
systemMessage: string
temperature: number
top_p: number
}
export function defaultSetting(): SettingsState {
const currentDate = new Date().toISOString().split('T')[0]
return {
systemMessage: 'You are ChatGPT, a large language model trained by OpenAI. Follow the user\'s instructions carefully. Respond using markdown.',
temperature: 0.8,
top_p: 1,
systemMessage: `You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.\nKnowledge cutoff: 2021-09-01\nCurrent date: ${currentDate}`,
}
}

Some files were not shown because too many files have changed in this diff Show More