Compare commits

...

37 Commits

Author SHA1 Message Date
Song367
96a8076246 k8s dev
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 59s
2025-10-27 10:53:34 +08:00
Song367
1b6a7541de 修改152
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 5m47s
2025-10-27 10:33:55 +08:00
Song367
f3ac9f1675 修改文件配置
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 56s
2025-09-28 10:38:32 +08:00
Song367
6cdc1c76ac 修改配置
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 55s
2025-09-28 10:35:45 +08:00
Song367
a2994f3905 健康检查
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 54s
2025-09-28 10:11:06 +08:00
Song367
5b0e17be2e 修改部署yaml
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 53s
2025-09-28 09:56:02 +08:00
Song367
de881414dc 取消安装包
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 58s
2025-09-26 18:23:58 +08:00
Song367
9822b9bfc9 迁移服务器
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 23m4s
2025-09-26 17:54:29 +08:00
Song367
53159ca24c 修改api配置
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 15s
2025-09-18 15:57:37 +08:00
Song367
fe13751624 增加未达到字数处理
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 4s
2025-08-18 19:15:59 +08:00
Song367
9aee63f624 添加15个字,末尾标点符号处理
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 3s
2025-08-14 18:27:27 +08:00
Song367
f299cf0145 英文冒号不设置为停顿符
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 55s
2025-07-08 17:31:35 +08:00
Song367
bbf7843a66 更改配置线路
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 53s
2025-07-08 15:44:57 +08:00
Song367
dee9dd7b08 延长判断字符15个
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 4s
2025-06-25 14:47:19 +08:00
Song367
0781e8bd86 文本返回读取内容
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 53s
2025-06-20 15:33:46 +08:00
Song367
602bb1fce2 音色配置修改
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 52s
2025-06-19 16:50:14 +08:00
Song367
a9230fcf81 去除日志
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 52s
2025-06-19 16:22:49 +08:00
Song367
ebd2f6fabb local
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 53s
2025-06-19 15:20:38 +08:00
Song367
16417c09a9 增加打印
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 52s
2025-06-19 11:47:05 +08:00
Song367
b73c2d2412 修改公证api token
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 52s
2025-06-19 11:27:03 +08:00
Song367
d52a8064db 新增匹配
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 52s
2025-06-18 20:29:37 +08:00
Song367
f4b35f4596 jijo
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 53s
2025-06-18 19:14:11 +08:00
Song367
7f89c584c0 audio
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 52s
2025-06-18 18:18:57 +08:00
Song367
ef15f5894e 删除无效输出
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 53s
2025-06-18 18:11:25 +08:00
Song367
99d6cb9f9f 增加文本长度
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 54s
2025-06-18 17:53:47 +08:00
Song367
1534a0228e 修复音频裁剪问题
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 3s
2025-06-18 17:22:26 +08:00
Song367
962b9d785a 阈值
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 53s
2025-06-18 17:17:19 +08:00
Song367
68919732db 修改阈值
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 3s
2025-06-18 16:44:14 +08:00
Song367
0c708da80d 修改音频阈值
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 52s
2025-06-18 16:23:42 +08:00
Song367
112a7dd70d 修改audio文件命名
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 2s
2025-06-18 16:15:28 +08:00
Song367
927406b6c8 修改URL 配置
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 54s
2025-06-18 15:06:47 +08:00
wangxvdong
76ac16eb13 * ) 增加挂载宿主机目录配置。
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 52s
2025-06-18 15:02:32 +08:00
wangxvdong
2dfb2cc519 * ) 增加挂载宿主机目录配置。
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 52s
2025-06-18 15:00:50 +08:00
Song367
a4d2cfe3e1 dockerfile copy .env
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 52s
2025-06-18 13:51:50 +08:00
Song367
1585a4491c 添加控制台输出
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 52s
2025-06-18 13:48:16 +08:00
Song367
d35dfba895 dockerfile 添加代理
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 53s
2025-06-18 13:40:53 +08:00
Song367
a632bc7332 修改dockerfile
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 4m45s
2025-06-18 13:23:08 +08:00
17 changed files with 848 additions and 82 deletions

16
.env
View File

@ -1,11 +1,15 @@
# LLM API Configuration
LLM_API_URL=http://tianchat.zenithsafe.com:5001/v1
LLM_API_KEY=app-k9WhnUvAPCVcSoPDEYVUxXgC
LLM_API_URL=http://101.133.149.116:8777/v1
LLM_API_KEY=app-7mh1IAGueaBodwdMflz8Omqv
LLMOurApiUrl=https://ark.cn-beijing.volces.com/api/v3/bots/chat/completions
LLMOurApiKey=e999a241-6bf3-4ee0-99a8-e4de9b617f28
MiniMaxApiKey=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiLkuIrmtbfpopzpgJTnp5HmioDmnInpmZDlhazlj7giLCJVc2VyTmFtZSI6IuadqOmqpSIsIkFjY291bnQiOiIiLCJTdWJqZWN0SUQiOiIxNzI4NzEyMzI0OTc5NjI2ODM5IiwiUGhvbmUiOiIxMzM4MTU1OTYxOCIsIkdyb3VwSUQiOiIxNzI4NzEyMzI0OTcxMjM4MjMxIiwiUGFnZU5hbWUiOiIiLCJNYWlsIjoiIiwiQ3JlYXRlVGltZSI6IjIwMjUtMDYtMTYgMTY6Mjk6NTkiLCJUb2tlblR5cGUiOjEsImlzcyI6Im1pbmltYXgifQ.D_JF0-nO89NdMZCYq4ocEyqxtZ9SeEdtMvbeSkZTWspt0XfX2QpPAVh-DI3MCPZTeSmjNWLf4fA_Th2zpVrj4UxWMbGKBeLZWLulNpwAHGMUTdqenuih3daCDPCzs0duhlFyQnZgGcEOGQ476HL72N2klujP8BUy_vfAh_Zv0po-aujQa5RxardDSOsbs49NTPEw0SQEXwaJ5bVmiZ5s-ysJ9pZWSEiyJ6SX9z3JeZHKj9DxHdOw5roZR8izo54e4IoqyLlzEfhOMW7P15-ffDH3M6HGiEmeBaGRYGAIciELjZS19ONNMKsTj-wXNGWtKG-sjAB1uuqkkT5Ul9Dunw
MiniMaxApiURL=https://api.minimaxi.com/v1/t2a_v2
APP_ID=1364994890450210816
APP_KEY=b4839cb2-cb81-4472-a2c1-2abf31e4bb27
APP_ID=1364966010532270080
APP_KEY=a72c98fa-cbe3-449e-b004-36523437bc5d
SIG_EXP=3600
FILE_URL=http://172.17.0.1:6200/
FILE_URL=http://14.103.114.237/gongzheng-backend/audio/
# Server Configuration
PORT=8080
PORT=80

24
.gitea/charts/Chart.yaml Normal file
View File

@ -0,0 +1,24 @@
apiVersion: v2
name: homeland
description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"

View File

@ -0,0 +1,22 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "homeland.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "homeland.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "homeland.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "homeland.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

View File

@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "homeland.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "homeland.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "homeland.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "homeland.labels" -}}
helm.sh/chart: {{ include "homeland.chart" . }}
{{ include "homeland.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "homeland.selectorLabels" -}}
app.kubernetes.io/name: {{ include "homeland.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "homeland.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "homeland.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,61 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "homeland.fullname" . }}
labels:
{{- include "homeland.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "homeland.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "homeland.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "homeland.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -0,0 +1,32 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "homeland.fullname" . }}
labels:
{{- include "homeland.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "homeland.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,61 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "homeland.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "homeland.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
pathType: {{ .pathType }}
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- else }}
serviceName: {{ $fullName }}
servicePort: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "homeland.fullname" . }}
labels:
{{- include "homeland.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "homeland.selectorLabels" . | nindent 4 }}

View File

@ -0,0 +1,12 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "homeland.serviceAccountName" . }}
labels:
{{- include "homeland.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "homeland.fullname" . }}-test-connection"
labels:
{{- include "homeland.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "homeland.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

84
.gitea/charts/values.yaml Normal file
View File

@ -0,0 +1,84 @@
# Default values for homeland.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: 172.16.54.94:5000/homeland
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: "29"
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
port: 80
ingress:
enabled: false
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: homeland.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
nodeSelector: {
kubernetes.io/hostname: devsvr1
}
tolerations: []
affinity: {}

View File

@ -3,7 +3,7 @@ run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on:
push:
branches:
- 'prod'
- 'prod-yantu'
env:
BUILD: staging
@ -11,7 +11,7 @@ env:
jobs:
Explore-Gitea-Actions:
runs-on: stream9
runs-on: yantoo-ci
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
@ -29,18 +29,13 @@ jobs:
- name: Build and push
uses: https://gitea.yantootech.com/neil/build-push-action@v6
with:
push: false
tags: gong-zheng-api:${{ gitea.run_id }}
- name: Run docker
push: true
tags: 14.103.114.237:30005/gongzheng-backend:${{ gitea.run_id }}
- name: Install
run: |
pwd
if [ "$(docker ps -q -f name=^gong-zheng-api$)" ]; then
docker stop gong-zheng-api
fi
docker run -d --rm --name gong-zheng-api \
-v /usr/share/fonts/opentype/noto:/usr/share/fonts \
-v $(pwd)/audio:/app/audio \
-p 6211:8080 \
-p 6212:8000 \
gong-zheng-api:${{ gitea.run_id }}
helm upgrade --install gongzheng-backend ./.gitea/charts \
--namespace gongzhengb \
--create-namespace \
--set image.repository=14.103.114.237:30005/gongzheng-backend \
--set image.tag=${{ gitea.run_id }}
- run: echo "🍏 This job's status is ${{ job.status }}."

View File

@ -1,56 +1,31 @@
# 构建 Go 服务
FROM golang:1.21-alpine AS go-builder
WORKDIR /app
WORKDIR /usr/src/app
# 安装必要的构建工具
RUN apk add --no-cache gcc musl-dev
# 设置 GOPROXY 环境变量
ENV GOPROXY=https://goproxy.cn,direct
# 复制 Go 项目文件
COPY . .
# 构建 Go 服务
RUN go build -o main ./main.go
# 构建 Python 服务
FROM python:3.11-slim
# 运行阶段
FROM alpine:latest
WORKDIR /app
WORKDIR /usr/src/app
# 安装必要的系统依赖
RUN apt-get update && apt-get install -y \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# 从构建阶段复制编译好的二进制文件和配置文件
COPY --from=go-builder /usr/src/app/main .
COPY --from=go-builder /usr/src/app/.env .
# 创建音频目录
RUN mkdir -p /app/audio
# 暴露端口(根据你的 API 服务端口修改)
EXPOSE 8080
# 复制 Python 文件服务器
COPY file_server.py .
# 从 go-builder 阶段复制编译好的 Go 服务
COPY --from=go-builder /app/main .
# 复制配置文件(如果有的话)
COPY --from=go-builder /app/config.yaml .
# 设置环境变量
ENV PORT=8000
ENV GO_PORT=8080
# 创建启动脚本
RUN echo '#!/bin/bash\n\
# 启动 Go 服务\n\
./main &\n\
# 启动 Python 文件服务器\n\
python file_server.py -p $PORT\n\
' > /app/start.sh && chmod +x /app/start.sh
# 暴露端口
EXPOSE 8000 8080
# 设置工作目录
WORKDIR /app
# 启动服务
CMD ["/app/start.sh"]
# 运行服务
CMD ["./main"]

View File

@ -4,11 +4,9 @@ services:
app:
build: .
ports:
- "8000:8000" # Python 文件服务器端口
- "8080:8080" # Go 服务端口
volumes:
- ./audio:/app/audio # 挂载音频目录
environment:
- PORT=8000
- GO_PORT=8080
restart: unless-stopped

View File

@ -62,7 +62,6 @@ class FileHandler(http.server.SimpleHTTPRequestHandler):
with open(file_path, 'rb') as f:
self.send_response(200)
self.send_header('Content-type', content_type)
self.send_header('Content-Disposition', f'attachment; filename="{os.path.basename(file_path)}"')
self.end_headers()
self.wfile.write(f.read())
except Exception as e:

22
main.go
View File

@ -1,6 +1,7 @@
package main
import (
"fmt"
"log"
"os"
"strconv"
@ -25,8 +26,12 @@ func main() {
MiniMaxApiKey: os.Getenv("MiniMaxApiKey"),
MiniMaxApiURL: os.Getenv("MiniMaxApiURL"),
FILE_URL: os.Getenv("FILE_URL"),
LLMOurApiUrl: os.Getenv("LLMOurApiUrl"),
LLMOurApiKey: os.Getenv("LLMOurApiKey"),
})
fmt.Println("config: ", llmService)
// Get token configuration from environment variables
sigExp, err := strconv.Atoi(os.Getenv("SIG_EXP"))
if err != nil {
@ -69,9 +74,24 @@ func main() {
router.POST("/speech/synthesize", llmHandler.SynthesizeSpeech)
router.GET("/stream-text", llmHandler.StreamText)
router.POST("/token", tokenHandler.GenerateToken)
// Define routes
router.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"status": "ok",
"message": "Service is healthy",
})
})
// Define routes
router.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{
"status": "ok",
"message": "Service is healthy",
})
})
// Serve static files
router.Static("/static", "./static")
router.Static("/audio", "./audio") // 添加音频文件访问服务
// Get host and port from environment variables
host := os.Getenv("HOST")
@ -80,7 +100,7 @@ func main() {
}
port := os.Getenv("PORT")
if port == "" {
port = "8080"
port = "80"
}
// Start server

View File

@ -24,6 +24,8 @@ type Config struct {
MiniMaxApiKey string
MiniMaxApiURL string
FILE_URL string
LLMOurApiUrl string
LLMOurApiKey string
}
// LLMService handles communication with the LLM API
@ -51,6 +53,7 @@ type RequestPayload struct {
ConversationID string `json:"conversation_id"`
Files []interface{} `json:"files"`
Audio string `json:"audio"`
LlmType string `json:"llm_type"`
}
// VoiceSetting represents voice configuration
@ -112,6 +115,18 @@ type SpeechResponse struct {
BaseResp BaseResponse `json:"base_resp"`
}
type LLMOurMessage struct {
Role string `json:"role"`
Content string `json:"content"`
}
type LLMOurRequestPayload struct {
Model string `json:"model"`
Stream bool `json:"stream"`
StreamOptions map[string]interface{} `json:"stream_options"`
Messages []LLMOurMessage `json:"messages"`
}
// NewLLMService creates a new instance of LLMService
func NewLLMService(config Config) *LLMService {
return &LLMService{
@ -130,6 +145,7 @@ func (s *LLMService) CallLLMAPI(data map[string]interface{}) (interface{}, error
ConversationID: getString(data, "conversation_id"),
Files: make([]interface{}, 0),
Audio: getString(data, "audio"),
LlmType: getString(data, "llm_type"),
}
fmt.Printf("前端传来的数据:%+v\n", payload)
@ -138,12 +154,51 @@ func (s *LLMService) CallLLMAPI(data map[string]interface{}) (interface{}, error
return nil, fmt.Errorf("error marshaling payload: %v", err)
}
req, err := http.NewRequest("POST", s.config.LLMApiURL+"/chat-messages", bytes.NewBuffer(jsonData))
// req, err := http.NewRequest("GET", "http://localhost:8080/stream-text", nil)
currentUrl := s.config.LLMApiURL + "/chat-messages"
fmt.Println(currentUrl)
req := &http.Request{}
if payload.LlmType == "ours" {
// 动态构造 messages
var messages []LLMOurMessage
if msgs, ok := data["messages"]; ok {
if arr, ok := msgs.([]interface{}); ok {
for _, m := range arr {
if mMap, ok := m.(map[string]interface{}); ok {
role, _ := mMap["role"].(string)
content, _ := mMap["content"].(string)
messages = append(messages, LLMOurMessage{Role: role, Content: content})
}
}
}
}
// fallback: 如果没有 messages则用 query 作为 user 消息
if len(messages) == 0 && payload.Query != "" {
messages = append(messages, LLMOurMessage{Role: "user", Content: payload.Query})
}
ourPayload := LLMOurRequestPayload{
Model: "bot-20250522162100-44785", // 可根据 data 或配置传入
Stream: true,
StreamOptions: map[string]interface{}{"include_usage": true},
Messages: messages,
}
jsonData, err = json.Marshal(ourPayload)
if err != nil {
return nil, fmt.Errorf("error marshaling ourPayload: %v", err)
}
currentUrl = s.config.LLMOurApiUrl
req, err = http.NewRequest("POST", currentUrl, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("error creating request: %v", err)
}
req.Header.Set("Authorization", "Bearer "+s.config.LLMOurApiKey)
req.Header.Set("Content-Type", "application/json")
return s.handleStreamingResponseV2(req, data, payload.Audio)
}
req, err = http.NewRequest("POST", currentUrl, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("error creating request: %v", err)
}
req.Header.Set("Authorization", "Bearer "+s.config.LLMApiKey)
req.Header.Set("Content-Type", "application/json")
@ -155,6 +210,135 @@ func (s *LLMService) CallLLMAPI(data map[string]interface{}) (interface{}, error
return s.handleNonStreamingResponse(req)
}
// processStreamSegment 处理流式文本分段、语音合成等逻辑,返回 new_message、audio、是否需要发送
func (s *LLMService) processStreamSegment(initialSessage *string, all_message *string, answer string, audio_type string) (string, string, bool) {
// 定义标点符号map
punctuations := map[string]bool{
",": true, "": true, // 逗号
".": true, "。": true, // 句号
"!": true, "": true, // 感叹号
"?": true, "": true, // 问号
";": true, "": true, // 分号
":": true, "": true, // 冒号
"、": true,
}
// 删除字符串前后的标点符号
trimPunctuation := func(s string) string {
if len(s) > 0 {
lastRune, size := utf8.DecodeLastRuneInString(s)
if punctuations[string(lastRune)] {
s = s[:len(s)-size]
}
}
return s
}
// 判断字符串是否包含标点符号
containsPunctuation := func(s string) bool {
for _, char := range s {
if punctuations[string(char)] {
return true
}
}
return false
}
// 按标点符号分割文本
splitByPunctuation := func(s string) []string {
var result []string
var current string
for _, char := range s {
if punctuations[string(char)] {
if current != "" {
result = append(result, current+string(char))
current = ""
}
} else {
current += string(char)
}
}
if current != "" {
result = append(result, current)
}
return result
}
*initialSessage += answer
*all_message += answer
new_message := ""
if containsPunctuation(*initialSessage) {
segments := splitByPunctuation(*initialSessage)
if len(segments) > 1 {
format_message := strings.Join(segments[:len(segments)-1], "")
if utf8.RuneCountInString(format_message) > 10 {
*initialSessage = segments[len(segments)-1]
new_message = strings.Join(segments[:len(segments)-1], "")
} else {
return "", "", false
}
} else {
if utf8.RuneCountInString(*initialSessage) > 10 {
new_message = *initialSessage
*initialSessage = ""
} else if utf8.RuneCountInString(*initialSessage) <= 10 && strings.HasSuffix(*initialSessage, "。") {
new_message = *initialSessage
*initialSessage = ""
} else {
return "", "", false
}
}
}
if new_message == "" {
return "", "", false
}
s_msg := strings.TrimSpace(new_message)
new_message = trimPunctuation(s_msg)
audio := ""
for i := 0; i < 1; i++ {
speechResp, err := s.SynthesizeSpeech(new_message, audio_type)
if err != nil {
fmt.Printf("Error synthesizing speech: %v\n", err)
break
}
fmt.Println("触发音频", speechResp)
audio = speechResp.Data.Audio
if audio != "" {
resp, err := http.Get(audio)
if err != nil {
fmt.Printf("Error downloading audio: %v\n", err)
} else {
defer resp.Body.Close()
audioBytes, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading audio data: %v\n", err)
} else {
originalPath := fmt.Sprintf("audio/original_%d.wav", time.Now().UnixNano())
if err := os.WriteFile(originalPath, audioBytes, 0644); err != nil {
fmt.Printf("Error saving original audio: %v\n", err)
}
audioBase64 := base64.StdEncoding.EncodeToString(audioBytes)
trimmedAudio, err := s.TrimAudioSilence(audioBase64)
if err != nil {
fmt.Printf("Error trimming audio silence: %v\n", err)
} else {
audio_path := fmt.Sprintf("trimmed_%d.wav", time.Now().UnixNano())
outputPath := "audio/" + audio_path
if err := s.SaveBase64AsWAV(trimmedAudio, outputPath); err != nil {
fmt.Printf("Error saving trimmed WAV file: %v\n", err)
}
audio = s.config.FILE_URL + audio_path
}
}
}
break
}
}
return s_msg, audio, true
}
// handleStreamingResponse processes streaming responses
func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]interface{}, audio_type string) (chan Message, error) {
resp, err := s.client.Do(req)
@ -167,6 +351,7 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
}
messageChan := make(chan Message, 100) // Buffered channel for better performance
all_message := ""
initialSessage := ""
go func() {
defer resp.Body.Close()
@ -200,6 +385,7 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
switch event {
case "message":
answer := getString(jsonData, "answer")
fmt.Println("源文本:", answer)
var audio string
// 定义标点符号map
@ -209,7 +395,7 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
"!": true, "": true, // 感叹号
"?": true, "": true, // 问号
";": true, "": true, // 分号
":": true, "": true, // 冒号
"": true, // 冒号
"、": true,
}
@ -256,6 +442,7 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
}
new_message := ""
initialSessage += answer
all_message += answer
if containsPunctuation(initialSessage) {
segments := splitByPunctuation(initialSessage)
// fmt.Printf("原始文本: %s\n", initialSessage)
@ -264,11 +451,32 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
// fmt.Printf("片段 %d: %s\n", i+1, segment)
// }
if len(segments) > 1 {
initialSessage = segments[len(segments)-1]
new_message = strings.Join(segments[:len(segments)-1], "")
format_message := strings.Join(segments[:len(segments)-1], "")
// 检查initialSessage的字符长度是否超过15个
if utf8.RuneCountInString(format_message) > 15 {
initialSessage = segments[len(segments)-1]
// 如果超过10个字符将其添加到new_message中并清空initialSessage
new_message = strings.Join(segments[:len(segments)-1], "")
// initialSessage = ""
} else {
if containsPunctuation(format_message) && utf8.RuneCountInString(format_message) > 10 {
initialSessage = segments[len(segments)-1]
new_message = strings.Join(segments[:len(segments)-1], "")
} else {
continue
}
}
} else {
new_message = initialSessage
initialSessage = ""
if utf8.RuneCountInString(initialSessage) > 15 {
new_message = initialSessage
initialSessage = ""
} else {
continue
}
}
// fmt.Printf("新消息: %s\n", new_message)
// fmt.Printf("剩余文本: %s\n", initialSessage)
@ -280,8 +488,8 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
s_msg := strings.TrimSpace(new_message)
// Trim punctuation from the message
new_message = trimPunctuation(s_msg)
// fmt.Println("new_message", new_message)
fmt.Println("new_message", new_message)
// println(new_message)
// 最多重试一次
for i := 0; i < 1; i++ {
speechResp, err := s.SynthesizeSpeech(new_message, audio_type)
@ -303,7 +511,7 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
fmt.Printf("Error reading audio data: %v\n", err)
} else {
// Save original audio first
originalPath := fmt.Sprintf("audio/original_%d.wav", time.Now().Unix())
originalPath := fmt.Sprintf("audio/original_%d.wav", time.Now().UnixNano())
if err := os.WriteFile(originalPath, audioBytes, 0644); err != nil {
fmt.Printf("Error saving original audio: %v\n", err)
}
@ -315,7 +523,7 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
fmt.Printf("Error trimming audio silence: %v\n", err)
} else {
// Save the trimmed audio as WAV file
audio_path := fmt.Sprintf("trimmed_%d.wav", time.Now().Unix())
audio_path := fmt.Sprintf("trimmed_%d.wav", time.Now().UnixNano())
outputPath := "audio/" + audio_path
if err := s.SaveBase64AsWAV(trimmedAudio, outputPath); err != nil {
fmt.Printf("Error saving trimmed WAV file: %v\n", err)
@ -326,12 +534,12 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
}
break // 获取到音频就退出
}
fmt.Println("audio is empty, retry", speechResp)
// fmt.Println("audio is empty, retry", speechResp)
// time.Sleep(1 * time.Second)
}
fmt.Println("所有消息:", all_message)
messageChan <- Message{
Answer: new_message,
Answer: s_msg,
IsEnd: false,
ConversationID: getString(jsonData, "conversation_id"),
TaskID: getString(jsonData, "task_id"),
@ -339,6 +547,96 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
AudioData: audio, // Update to use the correct path to audio data
}
case "message_end":
// 在流结束前,处理剩余的文本生成音频
if initialSessage != "" {
// 不管文本长度,直接生成音频
s_msg := strings.TrimSpace(initialSessage)
// 定义标点符号map
punctuations := map[string]bool{
",": true, "": true, // 逗号
".": true, "。": true, // 句号
"!": true, "": true, // 感叹号
"?": true, "": true, // 问号
";": true, "": true, // 分号
":": true, "": true, // 冒号
"、": true,
}
// 删除字符串前后的标点符号
trimPunctuation := func(s string) string {
if len(s) > 0 {
lastRune, size := utf8.DecodeLastRuneInString(s)
if punctuations[string(lastRune)] {
s = s[:len(s)-size]
}
}
return s
}
new_message := trimPunctuation(s_msg)
fmt.Println("最后一段文本生成音频:", new_message)
// 生成语音
var audio string
for i := 0; i < 1; i++ {
speechResp, err := s.SynthesizeSpeech(new_message, audio_type)
if err != nil {
fmt.Printf("Error synthesizing speech: %v\n", err)
break
}
fmt.Println("语音:", speechResp)
audio = speechResp.Data.Audio
if audio != "" {
// 下载并处理音频
resp, err := http.Get(audio)
if err != nil {
fmt.Printf("Error downloading audio: %v\n", err)
} else {
defer resp.Body.Close()
audioBytes, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading audio data: %v\n", err)
} else {
// 保存原始音频
originalPath := fmt.Sprintf("audio/original_%d.wav", time.Now().UnixNano())
if err := os.WriteFile(originalPath, audioBytes, 0644); err != nil {
fmt.Printf("Error saving original audio: %v\n", err)
}
// 静音裁剪
audioBase64 := base64.StdEncoding.EncodeToString(audioBytes)
trimmedAudio, err := s.TrimAudioSilence(audioBase64)
if err != nil {
fmt.Printf("Error trimming audio silence: %v\n", err)
} else {
audio_path := fmt.Sprintf("trimmed_%d.wav", time.Now().UnixNano())
outputPath := "audio/" + audio_path
if err := s.SaveBase64AsWAV(trimmedAudio, outputPath); err != nil {
fmt.Printf("Error saving trimmed WAV file: %v\n", err)
}
audio = s.config.FILE_URL + audio_path
}
}
}
break
}
}
// 发送最后一段文本的消息
messageChan <- Message{
Answer: s_msg,
IsEnd: false,
ConversationID: getString(jsonData, "conversation_id"),
TaskID: getString(jsonData, "task_id"),
ClientID: getString(data, "conversation_id"),
AudioData: audio,
}
// 清空剩余文本
initialSessage = ""
}
// 发送结束消息
messageChan <- Message{
Answer: "",
IsEnd: true,
@ -353,6 +651,95 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
return messageChan, nil
}
// handleStreamingResponseV2 适配新流式返回格式
func (s *LLMService) handleStreamingResponseV2(req *http.Request, data map[string]interface{}, audio_type string) (chan Message, error) {
resp, err := s.client.Do(req)
if err != nil {
return nil, fmt.Errorf("error making request: %v", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
messageChan := make(chan Message, 100)
all_message := ""
initialSessage := ""
go func() {
defer resp.Body.Close()
defer close(messageChan)
reader := bufio.NewReader(resp.Body)
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
fmt.Printf("Error reading line: %v\n", err)
continue
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
// line = strings.TrimSpace(line)
if strings.HasPrefix(line, "data:") {
line = strings.TrimSpace(strings.TrimPrefix(line, "data:"))
}
// fmt.Println("line: ", line)
if line == "[DONE]" {
messageChan <- Message{
Answer: "",
IsEnd: true,
ConversationID: getString(data, "conversation_id"),
TaskID: getString(data, "task_id"),
}
return
}
var jsonData map[string]interface{}
if err := json.Unmarshal([]byte(line), &jsonData); err != nil {
fmt.Printf("Error unmarshaling JSON: %v\n", err)
continue
}
choices, ok := jsonData["choices"].([]interface{})
if !ok || len(choices) == 0 {
continue
}
choice, ok := choices[0].(map[string]interface{})
if !ok {
continue
}
delta, ok := choice["delta"].(map[string]interface{})
if !ok {
continue
}
content, _ := delta["content"].(string)
if content == "" {
continue
}
new_message, audio, needSend := s.processStreamSegment(&initialSessage, &all_message, content, audio_type)
if !needSend {
continue
}
messageChan <- Message{
Answer: new_message,
IsEnd: false,
ConversationID: getString(data, "conversation_id"),
TaskID: getString(data, "task_id"),
ClientID: getString(data, "conversation_id"),
AudioData: audio,
}
}
}()
return messageChan, nil
}
// handleNonStreamingResponse processes non-streaming responses
func (s *LLMService) handleNonStreamingResponse(req *http.Request) (map[string]interface{}, error) {
resp, err := s.client.Do(req)
@ -424,7 +811,7 @@ func (s *LLMService) DeleteConversation(conversationID, user string) (map[string
// SynthesizeSpeech converts text to speech
func (s *LLMService) SynthesizeSpeech(text string, audio string) (*SpeechResponse, error) {
payload := SpeechRequest{
Model: "speech-02-turbo",
Model: "speech-02-hd",
Text: text,
Stream: false,
LanguageBoost: "auto",
@ -433,8 +820,8 @@ func (s *LLMService) SynthesizeSpeech(text string, audio string) (*SpeechRespons
VoiceID: audio,
Speed: 1,
Vol: 1,
Pitch: 0,
Emotion: "happy",
Pitch: -1,
Emotion: "neutral",
},
AudioSetting: AudioSetting{
SampleRate: 32000,