feat: home page
All checks were successful
Build / Build (push) Successful in 1m22s

This commit is contained in:
洛天依 2025-01-11 04:31:04 +00:00
parent f06f341d12
commit 644e1339fb
Signed by: luo
SSH Key Fingerprint: SHA256:V1KdsvGUpiKVfrJo1oHrAPnc/Z6k/6xgaZN7iTbYBl4
20 changed files with 402 additions and 251 deletions

21
.editorconfig Normal file
View File

@ -0,0 +1,21 @@
; https://editorconfig.org/
root = true
[*]
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
[{Makefile,go.mod,go.sum,*.go,.gitmodules}]
indent_style = tab
indent_size = 4
[{*.yml,*.yaml}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false

View File

@ -1,13 +1,6 @@
on: [push, pull_request, workflow_dispatch]
name: Build name: Build
on:
push:
branches:
- main
tags:
- v*
workflow_dispatch:
jobs: jobs:
build: build:
name: Build name: Build
@ -17,6 +10,9 @@ jobs:
contents: write contents: write
pull-requests: read pull-requests: read
checks: write checks: write
env:
DOCKER_REGISTRY: devops.lty.name
DOCKER_IMAGE: ${{ github.repository }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@ -26,36 +22,33 @@ jobs:
go-version: '1.23' go-version: '1.23'
- uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 - uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8
with: with:
version: v1.60 version: v1.63
- name: Build - name: build
run: make -j8 all && tree bin
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: builds-${{ github.sha }}
path: |
bin/*.zip
bin/*.tar.gz
bin/*.sha256sum
- name: Upload Release Asset
if: startsWith(github.ref, 'refs/tags/')
run: | run: |
gh release create ${GIT_TAG} bin/*.zip bin/*.tar.gz bin/*.sha256sum make build
cp -r bin ${DISTNAME}
cp -r agents ${DISTNAME}
tar --zstd -cvf ${DISTNAME}.tar.zst ${DISTNAME}
env: env:
GIT_TAG: ${{ github.ref_name }} DISTNAME: chatai-linux-amd64-${{ github.ref_name }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - if: startsWith(github.ref, 'refs/tags/')
uses: https://gitea.com/actions/release-action@21a5938ff2548f6472d89ba13b3cdd8af4c67068
with:
api_key: ${{ secrets.GITHUB_TOKEN }}
files: |
*.tar.zst
- uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 - uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5
- uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
with: with:
registry: ghcr.io registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ github.actor }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.PACKAGES_PUBLISH_TOKEN }}
- uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 - if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
platforms: linux/amd64,linux/arm64,linux/riscv64,linux/386
push: true push: true
tags: | tags: |
ghcr.io/${{ github.repository }}:${{ startsWith(github.ref, 'refs/tags/') && 'latest' || 'dev' }} ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ startsWith(github.ref, 'refs/tags/') && 'latest' || 'dev' }}
ghcr.io/${{ github.repository }}:${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || github.sha }} ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || github.sha }}

View File

@ -1,13 +1,7 @@
FROM --platform=$BUILDPLATFORM alpine:3.21 AS builder FROM alpine:3.21
RUN apk add --no-cache tzdata ca-certificates COPY ./bin/chatai /app/chatai
COPY ./agents /app/agents
FROM alpine:3.21 RUN apk add --no-cache tzdata ca-certificates
ARG TARGETPLATFORM WORKDIR /app
COPY bin/arch/${TARGETPLATFORM}/ai-agent /app/ai-agent EXPOSE 7120
COPY agents /app/agents CMD ["/app/chatai"]
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /usr/share/ca-certificates /usr/share/ca-certificates
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
WORKDIR /app
EXPOSE 7120
CMD ["/app/ai-agent"]

View File

@ -1,30 +1,10 @@
#!/usr/bin/env make -f #!/usr/bin/env make -f
AGENTS_DIR = agents OUTPUT := ./bin/
DIST_DIRECTORY = ./bin VERSION := $(shell git describe --tags 2>/dev/null || echo "git/$(shell git rev-parse --short HEAD 2>/dev/null || echo 'none')")
DIST_APPNAME = ai-agent CGO_ENABLED := 0
DIST_EXENAME = ai-agent BUILDINFO_CLASS := devops.lty.name/luo/chatai/internal/config
BUILDINFO_CLASS = github.com/ltylab/ai-agent/internal/conf
BUILDINFO_VERSION := $(shell git describe --tags 2>/dev/null || echo "git/$(shell git rev-parse --short HEAD 2>/dev/null || echo 'none')")
CROSS_BUILD_TRIPLES = darwin/amd64 \
darwin/arm64 \
windows/386 \
windows/amd64 \
windows/arm64 \
linux/386 \
linux/amd64 \
linux/arm64 \
linux/loong64 \
linux/mips \
linux/mips64 \
linux/mips64le \
linux/mipsle \
linux/riscv64 \
freebsd/386 \
freebsd/amd64 \
freebsd/arm64 \
freebsd/riscv64
LDFLAGS = -s -w LDFLAGS = -s -w
LDFLAGS += -X "$(BUILDINFO_CLASS).BuildVersion=$(BUILDINFO_VERSION)" LDFLAGS += -X "$(BUILDINFO_CLASS).BuildVersion=$(VERSION)"
LDFLAGS += -X "$(BUILDINFO_CLASS).BuildTime=$(shell date '+%Y-%m-%d %H:%M:%S %Z')" LDFLAGS += -X "$(BUILDINFO_CLASS).BuildTime=$(shell date '+%Y-%m-%d %H:%M:%S %Z')"
LDFLAGS += -X "$(BUILDINFO_CLASS).BuildMode=release" LDFLAGS += -X "$(BUILDINFO_CLASS).BuildMode=release"
@ -36,7 +16,12 @@ build_debug: build
.PHONY: build .PHONY: build
build: clean install build: clean install
CGO_ENABLED=0 go build -ldflags '$(LDFLAGS)' -o $(DIST_DIRECTORY)/$(DIST_EXENAME) CGO_ENABLED=$(CGO_ENABLED) go build -ldflags '$(LDFLAGS)' -o $(OUTPUT)
.PHONY: run
run: install
go run .
.PHONY: install .PHONY: install
@ -46,26 +31,4 @@ install:
.PHONY: clean .PHONY: clean
clean: clean:
rm -rvf $(DIST_DIRECTORY) rm -rvf $(OUTPUT)
.PHONY: all $(CROSS_BUILD_TRIPLES)
all: clean install $(CROSS_BUILD_TRIPLES)
$(CROSS_BUILD_TRIPLES): GOOS = $(word 1,$(subst /, ,$@))
$(CROSS_BUILD_TRIPLES): GOARCH = $(word 2,$(subst /, ,$@))
$(CROSS_BUILD_TRIPLES): CROSS_DIST_DIRNAME = $(DIST_APPNAME)-$(subst /,.,$(BUILDINFO_VERSION)-$(GOOS)-$(GOARCH))
$(CROSS_BUILD_TRIPLES): CROSS_DIST_EXENAME = $(DIST_EXENAME)$(if $(filter $(GOOS),windows),.exe,)
$(CROSS_BUILD_TRIPLES): CROSS_DIST_ARCNAME = $(CROSS_DIST_DIRNAME).$(if $(filter $(GOOS),windows),zip,tar.gz)
$(CROSS_BUILD_TRIPLES):
$(MAKE) build GOOS=$(GOOS) GOARCH=$(GOARCH) \
DIST_DIRECTORY=$(DIST_DIRECTORY)/$(CROSS_DIST_DIRNAME) \
DIST_EXENAME=$(CROSS_DIST_EXENAME) && \
cp -r $(AGENTS_DIR) $(DIST_DIRECTORY)/$(CROSS_DIST_DIRNAME)/ && \
mkdir -p $(DIST_DIRECTORY)/arch/$(GOOS)/$(GOARCH) && \
cp $(DIST_DIRECTORY)/$(CROSS_DIST_DIRNAME)/$(CROSS_DIST_EXENAME) $(DIST_DIRECTORY)/arch/$(GOOS)/$(GOARCH)/$(CROSS_DIST_EXENAME) && \
if [ "$(GOOS)" = "windows" ]; then \
cd $(DIST_DIRECTORY) && zip -r $(CROSS_DIST_ARCNAME) $(CROSS_DIST_DIRNAME) && cd -; \
else \
tar -cvzf $(DIST_DIRECTORY)/$(CROSS_DIST_ARCNAME) -C $(DIST_DIRECTORY) $(CROSS_DIST_DIRNAME); \
fi && \
sh -c "cd $(DIST_DIRECTORY) && sha256sum $(CROSS_DIST_ARCNAME) > $(CROSS_DIST_ARCNAME).sha256sum";

View File

@ -1,35 +1,34 @@
# Luo Tianyi Codelabs AI Agent # Chat AI
提供类似 QQ 界面的公共 AI 聊天机器人,基于 Google Gemini 模型。 提供类似 QQ 界面的公共 AI 聊天机器人,基于 Google Gemini 模型。
**注意** **注意**
- 不支持视频、图片、音频等多模态输入。 - 不支持视频、图片、音频等多模态输入。
- 完全公开、无频率限制与身份认证。建议使用免费的 API Key 并禁用结算账号。 - 完全公开、无频率限制与身份认证。建议使用免费的 API Key 并禁用结算账号。
- 暂时没有列出所有模型的功能。
## 使用方法 ## 使用方法
GitHub Release 下载最新版本的压缩包,解压缩。你可以使用 `-h` 参数查看帮助信息: 「发行版」下载最新版本的压缩包,解压缩。你可以使用 `-h` 参数查看帮助信息:
```bash ```bash
./ai-agent-web -h ./chatai -h
``` ```
要正常启动服务器,您需要设置环境变量 `GOOGLE_AI_KEY`。您可以在 [Google AI Studio](https://aistudio.google.com) 免费获得一个(需要 Google 账号)。 要正常启动服务器,您需要设置环境变量 `GOOGLE_AI_KEY`。您可以在 [Google AI Studio](https://aistudio.google.com) 免费获得一个(需要 Google 账号)。
```bash ```bash
export GOOGLE_AI_KEY=your-key export GOOGLE_AI_KEY=your-key
./ai-agent-web ./chatai
``` ```
您也可以使用 Docker 运行: 您也可以使用 Docker 运行:
```bash ```bash
docker run -d -p 57120:7120 -e GOOGLE_AI_KEY=your-key \ docker run -d -p 57120:7120 -e GOOGLE_AI_KEY=your-key \
--name ai-agent-web ghcr.io/github.com/ltylab/ai-agent --name chatai lty.name/chatai
``` ```
如果需要自定义 AI Agent请将您的 AI Agent 配置文件映射到 `/app/agents` 目录: 如果需要自定义 AI Agent请将您的 AI Agent 配置文件映射到 `/app/agents` 目录:
```bash ```bash
docker run -d -p 57120:7120 -e GOOGLE_AI_KEY=your-key \ docker run -d -p 57120:7120 -e GOOGLE_AI_KEY=your-key \
-v /path/to/your/agents:/app/agents \ -v /path/to/your/agents:/app/agents \
--name ai-agent-web ghcr.io/github.com/ltylab/ai-agent --name chatai lty.name/chatai
``` ```
可以通过 `-model` 参数修改模型,推荐使用下面两个免费模型: 可以通过 `-model` 参数修改模型,推荐使用下面两个免费模型:
@ -55,6 +54,7 @@ agents
"id": "luo", "id": "luo",
"name": "洛天依AI", "name": "洛天依AI",
"desc": "世界第一的 ∞ 吃货殿下~", "desc": "世界第一的 ∞ 吃货殿下~",
"footer": "此 AI 并非洛天依官方提供,请勿用于商业用途。",
"primaryColor": "#6cb6df", "primaryColor": "#6cb6df",
"secondaryColor": "#66ccff", "secondaryColor": "#66ccff",
"accentColor": "#a62f35" "accentColor": "#a62f35"
@ -65,6 +65,7 @@ agents
- `id` 是 Agent ID必须和文件夹名称一致。 - `id` 是 Agent ID必须和文件夹名称一致。
- `name` 是 Agent 名称,将显示在聊天界面。 - `name` 是 Agent 名称,将显示在聊天界面。
- `desc` 是 Agent 描述,将显示在聊天界面。 - `desc` 是 Agent 描述,将显示在聊天界面。
- `footer` 是 Agent 页脚,用于显示版权信息、免责声明等。
- `primaryColor` 是主色调,将用于聊天界面的背景色。可选,如果未设置则使用默认值。 - `primaryColor` 是主色调,将用于聊天界面的背景色。可选,如果未设置则使用默认值。
- `secondaryColor` 是次色调,将用于聊天界面的背景色。可选,如果未设置则使用默认值。 - `secondaryColor` 是次色调,将用于聊天界面的背景色。可选,如果未设置则使用默认值。
- `accentColor` 是强调色,将用于聊天界面的背景色。可选,如果未设置则使用默认值。 - `accentColor` 是强调色,将用于聊天界面的背景色。可选,如果未设置则使用默认值。

View File

@ -1,5 +1,6 @@
{ {
"id": "ldk", "id": "ldk",
"name": "李迪克AI", "name": "李迪克AI",
"desc": "我跟你讲!我家星尘宝宝,可爱(*´▽`*)" "desc": "我跟你讲!我家星尘宝宝,可爱(*´▽`*)",
"footer": "AI生成的内容不代表 <a href=\"https://space.bilibili.com/882467\" target=\"_blank\" rel=\"noopener noreferrer\">李迪克</a> 的观点 | 数据来源: <a href=\"https://cyberldk.com\" target=\"_blank\" rel=\"noopener noreferrer\">CyberLDK</a>"
} }

View File

@ -2,6 +2,7 @@
"id": "luo", "id": "luo",
"name": "洛天依AI", "name": "洛天依AI",
"desc": "世界第一的 ∞ 吃货殿下~", "desc": "世界第一的 ∞ 吃货殿下~",
"footer": "此 AI 并非官方制作 | 请勿用于不当或商业用途",
"primaryColor": "#6cb6df", "primaryColor": "#6cb6df",
"secondaryColor": "#66ccff", "secondaryColor": "#66ccff",
"accentColor": "#a62f35" "accentColor": "#a62f35"

2
go.mod
View File

@ -1,4 +1,4 @@
module github.com/ltylab/ai-agent module devops.lty.name/luo/chatai
go 1.23.3 go 1.23.3

View File

@ -7,9 +7,9 @@ import (
"path" "path"
"regexp" "regexp"
"github.com/ltylab/ai-agent/internal/conf" "devops.lty.name/luo/chatai/internal/config"
"github.com/ltylab/ai-agent/internal/log" "devops.lty.name/luo/chatai/internal/log"
"github.com/ltylab/ai-agent/web/assets" "devops.lty.name/luo/chatai/web/assets"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -24,6 +24,9 @@ type AgentConf struct {
// AgentDescription is a short description of the agent. // AgentDescription is a short description of the agent.
AgentDescription string `json:"desc"` AgentDescription string `json:"desc"`
// FooterHTML is the HTML content to be displayed in the footer of the agent chat page.
FooterHTML string `json:"footer"`
// PrimaryColor is the primary color for the agent chat page. // PrimaryColor is the primary color for the agent chat page.
PrimaryColor string `json:"primaryColor"` PrimaryColor string `json:"primaryColor"`
@ -78,11 +81,11 @@ func (a *AgentConf) ReadAgent() *Agent {
func LoadAgentFromFile(agentJSONPath string) (*AgentConf, error) { func LoadAgentFromFile(agentJSONPath string) (*AgentConf, error) {
conf := &AgentConf{ conf := &AgentConf{
AgentID: uuid.New().String(), AgentID: uuid.New().String(),
AgentName: conf.DefaultAgentName, AgentName: config.DefaultAgentName,
AgentDescription: conf.DefaultAgentDescription, AgentDescription: config.DefaultAgentDescription,
PrimaryColor: conf.DefaultAgentPrimaryColor, PrimaryColor: config.DefaultAgentPrimaryColor,
SecondaryColor: conf.DefaultAgentSecondaryColor, SecondaryColor: config.DefaultAgentSecondaryColor,
AccentColor: conf.DefaultAgentAccentColor, AccentColor: config.DefaultAgentAccentColor,
Dir: path.Dir(agentJSONPath), Dir: path.Dir(agentJSONPath),
} }

View File

@ -1,22 +0,0 @@
package conf
var (
BuildVersion = "dev"
BuildTime = "<unknown>"
BuildMode = "development"
PackageLicense = "Unlicense"
PackageCopyright = "This is an unlicensed software (under public domain) by Luo Tianyi Codelabs <https://lty.name/>."
DefaultListen = ":7120"
DefaultAgentsDir = "agents"
DefaultLogLevel = "dbg"
DefaultMsgLogPath = "logs"
DefaultAgentName = "未名"
DefaultAgentDescription = "这个 Agent 没有名字"
DefaultAgentPrimaryColor = "#444e8d"
DefaultAgentSecondaryColor = "#9f9ff5"
DefaultAgentAccentColor = "#eeaf5b"
DefaultGoogleAIModel = "gemini-1.5-flash"
)

21
internal/config/conf.go Normal file
View File

@ -0,0 +1,21 @@
package config
var (
BuildVersion = "dev"
BuildTime = "<unknown>"
BuildMode = "development"
Copyright = "This is an unlicensed software (under public domain) by Luo Tianyi Codelabs <https://lty.name/>."
FlagListen = ":7120"
FlagAgentsDir = "agents"
FlagLogLevel = "dbg"
FlagMsgLogPath = "logs"
DefaultAgentName = "未名"
DefaultAgentDescription = "这个 Agent 没有名字"
DefaultAgentPrimaryColor = "#444e8d"
DefaultAgentSecondaryColor = "#9f9ff5"
DefaultAgentAccentColor = "#eeaf5b"
DefaultGoogleAIModel = "gemini-1.5-flash"
)

View File

@ -8,8 +8,8 @@ import (
"path" "path"
"time" "time"
"github.com/ltylab/ai-agent/internal/agent" "devops.lty.name/luo/chatai/internal/agent"
"github.com/ltylab/ai-agent/internal/log" "devops.lty.name/luo/chatai/internal/log"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/generative-ai-go/genai" "github.com/google/generative-ai-go/genai"

View File

@ -1,15 +1,15 @@
package server package server
import ( import (
"html/template" "text/template"
"net/http" "net/http"
"os" "os"
"github.com/ltylab/ai-agent/internal/agent" "devops.lty.name/luo/chatai/internal/agent"
"github.com/ltylab/ai-agent/internal/log" "devops.lty.name/luo/chatai/internal/log"
"github.com/ltylab/ai-agent/web" "devops.lty.name/luo/chatai/web"
"github.com/ltylab/ai-agent/web/assets" "devops.lty.name/luo/chatai/web/assets"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -38,11 +38,32 @@ func Setup(e *gin.Engine, agentsDir string, msgLogPath string) {
} }
e.StaticFS("/assets", http.FS(assets.AssetsFS)) e.StaticFS("/assets", http.FS(assets.AssetsFS))
e.GET("", s.handleHomepageWithRendenedTemplate)
e.GET("/:agentID", s.handleWithRendenedTemplate) e.GET("/:agentID", s.handleWithRendenedTemplate)
e.GET("/:agentID/avatar.webp", s.handleAvatar) e.GET("/:agentID/avatar.webp", s.handleAvatar)
e.POST("/:agentID/chat", s.handleChat) e.POST("/:agentID/chat", s.handleChat)
} }
func (s *Server) handleHomepageWithRendenedTemplate(c *gin.Context) {
tmpl, err := template.New("home").Parse(web.HomeLayout)
if err != nil {
log.T("server/tmpl").Errf("Failed to parse template: %v", err)
c.String(http.StatusInternalServerError, "server error")
return
}
err = tmpl.Execute(c.Writer, s.agents)
if err != nil {
log.T("server/tmpl").Errf("Failed to render template: %v", err)
c.String(http.StatusInternalServerError, "server error")
return
}
c.Status(http.StatusOK)
c.Header("Content-Type", "text/html; charset=utf-8")
c.Writer.Flush()
c.Abort()
}
func (s *Server) handleWithRendenedTemplate(c *gin.Context) { func (s *Server) handleWithRendenedTemplate(c *gin.Context) {
agentID := c.Param("agentID") agentID := c.Param("agentID")
agentConf, ok := s.agents[agentID] agentConf, ok := s.agents[agentID]
@ -51,7 +72,7 @@ func (s *Server) handleWithRendenedTemplate(c *gin.Context) {
return return
} }
tmpl, err := template.New("tmpl.html").Parse(web.Template) tmpl, err := template.New("agent").Parse(web.AgentLayout)
if err != nil { if err != nil {
log.T("server/tmpl").Errf("Failed to parse template: %v", err) log.T("server/tmpl").Errf("Failed to parse template: %v", err)
c.String(http.StatusInternalServerError, "server error") c.String(http.StatusInternalServerError, "server error")

22
main.go
View File

@ -4,19 +4,19 @@ import (
"flag" "flag"
"os" "os"
"github.com/ltylab/ai-agent/internal/conf" "devops.lty.name/luo/chatai/internal/config"
"github.com/ltylab/ai-agent/internal/log" "devops.lty.name/luo/chatai/internal/log"
"github.com/ltylab/ai-agent/internal/server" "devops.lty.name/luo/chatai/internal/server"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func main() { func main() {
listen := flag.String("listen", conf.DefaultListen, "http listen address") listen := flag.String("listen", config.FlagListen, "http listen address")
agentsDir := flag.String("agents", conf.DefaultAgentsDir, "directory to store agent configurations") agentsDir := flag.String("agents", config.FlagAgentsDir, "directory to store agent configurations")
logLevel := flag.String("level", conf.DefaultLogLevel, "log level (dbg, inf, wrn, err)") logLevel := flag.String("level", config.FlagLogLevel, "log level (dbg, inf, wrn, err)")
msgLogPath := flag.String("msglog", conf.DefaultMsgLogPath, "audit log path") msgLogPath := flag.String("msglog", config.FlagMsgLogPath, "audit log path")
model := flag.String("model", conf.DefaultGoogleAIModel, "Google AI model (gemini-1.5-flash, gemini-2.0-flash-exp)") model := flag.String("model", config.DefaultGoogleAIModel, "Google AI model (gemini-1.5-flash, gemini-2.0-flash-exp)")
flag.Parse() flag.Parse()
server.GoogleAIKey = os.Getenv("GOOGLE_AI_KEY") server.GoogleAIKey = os.Getenv("GOOGLE_AI_KEY")
@ -27,11 +27,11 @@ func main() {
server.GoogleAIModel = *model server.GoogleAIModel = *model
log.Setup(log.LogLevel(log.ParseLogLevel(*logLevel))) log.Setup(log.LogLevel(log.ParseLogLevel(*logLevel)))
log.T("main").Inff("AI-Agent version %s, %s mode (build %s)", conf.BuildVersion, conf.BuildMode, conf.BuildTime) log.T("main").Inff("AI-Agent version %s, %s mode (build %s)", config.BuildVersion, config.BuildMode, config.BuildTime)
log.T("main").Inff("%s", conf.PackageCopyright) log.T("main").Inff("%s", config.Copyright)
log.SetupGin1() log.SetupGin1()
if conf.BuildMode == "release" { if config.BuildMode == "release" {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
} }

View File

@ -248,7 +248,7 @@
<aside> <aside>
<button>_</button> <button>_</button>
<button></button> <button></button>
<button>×</button> <button onclick="location.href = '/'">×</button>
</aside> </aside>
</header> </header>
<article> <article>
@ -264,7 +264,8 @@
</footer> </footer>
</main> </main>
<div class="copy"> <div class="copy">
以上内容为 AI 生成,请注意辨别。<br>为了安全和审计需要,我们可能记录您与 AI 的对话内容和您的 IP 地址。请勿向模型分享敏感信息。 {{.FooterHTML}}<br>
以上内容为 AI 生成,请注意辨别。<br>为了安全和审计需要,我们可能记录您与 AI 的对话内容和您的 IP 地址。请勿向 AI 分享敏感信息。
</div> </div>
<template id="chat-record"> <template id="chat-record">
<section> <section>

View File

@ -4,5 +4,8 @@ import (
_ "embed" _ "embed"
) )
//go:embed tmpl.html //go:embed home.html
var Template string var HomeLayout string
//go:embed agent.html
var AgentLayout string

150
web/home.html Normal file
View File

@ -0,0 +1,150 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="renderer" content="webkit">
<meta http-equiv="Cache-Control" content="no-siteapp">
<meta name="google" content="notranslate">
<link rel="icon" type="image/png" href="/assets/logo.png">
<title>选择一个 AI Agent 开始聊天</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
font-family: -apple-system, 'Helvetica Neue', Helvetica, 'Nimbus Sans L', Arial, 'Liberation Sans',
'PingFang SC', 'Hiragino Sans GB', 'Source Han Sans CN', 'Source Han Sans SC', 'Microsoft YaHei', 'Wenquanyi Micro Hei', 'WenQuanYi Zen Hei',
'ST Heiti', SimHei, 'WenQuanYi Zen Hei Sharp', sans-serif;
background-color: #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: 100vh;
}
h1 {
margin: 0;
margin-bottom: 3rem;
font-size: 1.8rem;
color: #3c3c3c;
text-align: center;
font-weight: 400;
}
main {
width: 100%;
height: 40%;
min-height: 400px;
flex-wrap: nowrap;
padding: 0 2rem;
}
main .container {
height: 100%;
padding: 1rem;
display: flex;
flex-wrap: nowrap;
overflow-x: auto;
gap: 2.5rem;
justify-content: space-evenly;
align-items: center;
-ms-overflow-style: none;
scrollbar-width: none;
}
main .container::-webkit-scrollbar {
display: none;
}
section {
height: 100%;
background: #fcfcfc;
min-width: 300px;
max-width: 300px;
padding: 1rem;
border-radius: 1rem;
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
text-align: center;
}
section img {
margin-top: 2rem;
margin-bottom: 1.5rem;
width: 3.5rem;
height: 3.5rem;
border-radius: .5rem;
}
section h2 {
margin: 0;
font-size: 1.5rem;
font-weight: 400;
margin-bottom: 1.5rem;
}
section span {
font-size: 0.8rem;
color: #666;
margin-bottom: 1.5rem;
}
section a {
display: block;
width: 100%;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
text-decoration: none;
transition: all 0.3s;
justify-self: flex-end;
cursor: pointer;
margin-bottom: .5rem;
transition: all 0.3s ease;
}
{{range .}}
.chat-btn__{{.AgentID}} {
background: {{.PrimaryColor}};
color: white;
}
.chat-btn__{{.AgentID}}:hover {
background: {{.SecondaryColor}};
}
{{end}}
</style>
</head>
<body>
<h1>选择一个 AI Agent 开始聊天</h1>
<main>
<div class="container">
{{range .}}
<section>
<div>
<img src="/{{.AgentID}}/avatar.webp" alt="{{.AgentName}} 头像">
<h2>{{.AgentName}}</h2>
<span>{{.AgentDescription}}</span>
</div>
<a href="/{{.AgentID}}" class="chat-btn__{{.AgentID}}">开始聊天</a>
</section>
{{end}}
</div>
</main>
<script>
window.addEventListener("wheel", function (e) {
const container = document.querySelector(".container");
if (e.deltaY > 0) container.scrollLeft += 100;
else container.scrollLeft -= 100;
});
</script>
</body>
</html>