diff --git a/Dockerfile b/Dockerfile index a04cb13..f6b9f7a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # build stage -FROM golang:1.21 AS builder +FROM --platform=$BUILDPLATFORM golang:1.21 AS builder ENV GOPROXY=https://goproxy.io,direct WORKDIR /data COPY go.mod go.sum ./ @@ -19,4 +19,4 @@ WORKDIR /data COPY --from=builder /data/bin/aidea-server /usr/local/bin/ EXPOSE 8080 -ENTRYPOINT ["/usr/local/bin/aidea-server", "--conf", "/etc/aidea.yaml"] \ No newline at end of file +ENTRYPOINT ["/usr/local/bin/aidea-server", "--conf", "/etc/aidea.yaml"] diff --git a/config/config.go b/config/config.go index c848bbb..b75f929 100644 --- a/config/config.go +++ b/config/config.go @@ -240,6 +240,8 @@ type Config struct { // 字体文件路径 FontPath string `json:"font_path" yaml:"font_path"` + // 服务状态页面 + ServiceStatusPage string `json:"service_status_page" yaml:"service_status_page"` } func (conf *Config) SupportProxy() bool { @@ -493,7 +495,8 @@ func Register(ins *app.App) { BeichouPrompt: strings.TrimSpace(ctx.String("virtual-model-beichou-prompt")), }, - FontPath: ctx.String("font-path"), + FontPath: ctx.String("font-path"), + ServiceStatusPage: ctx.String("service-status-page"), } }) } diff --git a/config/flag.go b/config/flag.go index c2bd4ad..f36f49c 100644 --- a/config/flag.go +++ b/config/flag.go @@ -199,4 +199,5 @@ func initCmdFlags(ins *app.App) { ins.AddStringFlag("price-table-file", "", "价格表文件路径,留空则使用默认价格表") ins.AddStringFlag("font-path", "", "字体文件路径") + ins.AddStringFlag("service-status-page", "", "服务状态页面,留空则不启用服务状态页面") } diff --git a/docker-build.sh b/docker-build.sh index 4db86cf..45d8502 100755 --- a/docker-build.sh +++ b/docker-build.sh @@ -1,13 +1,6 @@ #!/usr/bin/env bash -VERSION=1.0.6 -VERSION_DATE=202310091100 +VERSION=1.0.9 -docker build -t mylxsw/aidea-server:$VERSION . -docker tag mylxsw/aidea-server:$VERSION mylxsw/aidea-server:$VERSION_DATE -docker tag mylxsw/aidea-server:$VERSION mylxsw/aidea-server:latest - -docker push mylxsw/aidea-server:$VERSION -docker push mylxsw/aidea-server:$VERSION_DATE -docker push mylxsw/aidea-server:latest +docker buildx build --platform=linux/amd64,linux/arm64 -t mylxsw/aidea-server:$VERSION . --push diff --git a/internal/jobs/healthcheck.go b/internal/jobs/healthcheck.go index 8b6f7ad..8848467 100644 --- a/internal/jobs/healthcheck.go +++ b/internal/jobs/healthcheck.go @@ -3,6 +3,7 @@ package jobs import ( "context" "fmt" + "github.com/mylxsw/aidea-server/pkg/ai/getimgai" "github.com/mylxsw/aidea-server/pkg/ai/stabilityai" "github.com/mylxsw/aidea-server/pkg/dingding" "time" @@ -11,7 +12,13 @@ import ( "github.com/mylxsw/asteria/log" ) -func HealthCheckJob(ctx context.Context, conf *config.Config, ding *dingding.Dingding, stab *stabilityai.StabilityAI) error { +func HealthCheckJob( + ctx context.Context, + conf *config.Config, + ding *dingding.Dingding, + stab *stabilityai.StabilityAI, + getimg *getimgai.GetimgAI, +) error { // TODO 这里添加一些检查任务,如接口的余额查询,通知 // Stability.ai 账号余额不足预警 @@ -30,6 +37,22 @@ func HealthCheckJob(ctx context.Context, conf *config.Config, ding *dingding.Din } } + // Getimg.ai 账号余额不足预警 + if conf.EnableGetimgAI { + balance, err := queryGetimgAIBalance(ctx, getimg) + if err != nil { + log.Errorf("查询 Getimg.ai 账号余额失败: %v", err) + } else { + if balance <= 1 { + title := "Getimg.ai 账号余额不足,请及时充值" + content := fmt.Sprintf("Getimg.ai 当前账户余额 $%.2f,已低于预警值 $1,请及时充值", balance) + if err := ding.Send(dingding.NewMarkdownMessage(title, content, []string{})); err != nil { + log.WithFields(log.Fields{"content": content}).Errorf("send dingding message failed: %s", err) + } + } + } + } + return nil } @@ -39,3 +62,10 @@ func queryStabilityAIBalance(ctx context.Context, stab *stabilityai.StabilityAI) return stab.AccountBalance(ctx) } + +func queryGetimgAIBalance(ctx context.Context, getimg *getimgai.GetimgAI) (float64, error) { + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + return getimg.AccountBalance(ctx) +} diff --git a/migrate/data/20231129_dml.go b/migrate/data/20231129_dml.go index 306dc45..4557f46 100644 --- a/migrate/data/20231129_dml.go +++ b/migrate/data/20231129_dml.go @@ -414,6 +414,8 @@ func Migrate20231129DML(m *migrate.Manager) { "INSERT INTO `creative_gallery` (`id`, `user_id`, `username`, `hot_value`, `creative_history_id`, `creative_type`, `meta`, `prompt`, `negative_prompt`, `answer`, `tags`, `ref_count`, `star_level`, `status`, `created_at`, `updated_at`) VALUES (625,24,'普罗米',39472,12122,2,'{\\\"mode\\\": \\\"canny\\\", \\\"steps\\\": 30, \\\"model_id\\\": \\\"stable-diffusion-xl-1024-v1-0\\\", \\\"filter_id\\\": 101, \\\"model_name\\\": \\\"Stable Diffusion XL v1.0\\\", \\\"image_ratio\\\": \\\"1:1\\\", \\\"real_prompt\\\": \\\"The illustration depicts a human heart made of translucent glass, standing on a pedestal amid the stormy waves. A ray of sunlight shines through the clouds, illuminating the heart and revealing the small universe within. the horizon is engraved with a bold line \\\\\\\"Find the universe within you.\\\\\\\"\\\", \\\"real_negative_prompt\\\": \\\"out of frame, lowres, text, error, cropped, worst quality, low quality, jpeg artifacts, ugly, duplicate, morbid, mutilated, out of frame, extra fingers, mutated hands, poorly drawn hands, poorly drawn face, mutation, deformed, blurry, dehydrated, bad anatomy, bad proportions, extra limbs, cloned face, disfigured, gross proportions, malformed limbs, missing arms, missing legs, extra arms, extra legs, fused fingers, too many fingers, long neck, username, watermark, signature\\\"}','这幅插画描绘了一颗由半透明玻璃制成的人心,矗立在惊涛骇浪中的基座上。一缕阳光穿透云层,照亮了心脏,揭示了其中的小宇宙。地平线上镌刻着一行醒目的大字 「Find the universe within you」。','','[\\\"https://ssl.aicode.cc/ai-server/24/20231022/aigc88317993-1cc8-fe77-a2a5-5da22d4860de.png\\\"]',NULL,NULL,NULL,1,'2023-10-21 17:03:18','2023-11-29 03:00:01');", "INSERT INTO `creative_gallery` (`id`, `user_id`, `username`, `hot_value`, `creative_history_id`, `creative_type`, `meta`, `prompt`, `negative_prompt`, `answer`, `tags`, `ref_count`, `star_level`, `status`, `created_at`, `updated_at`) VALUES (626,24,'普罗米',37754,12124,2,'{\\\"mode\\\": \\\"canny\\\", \\\"steps\\\": 30, \\\"model_id\\\": \\\"stable-diffusion-xl-1024-v1-0\\\", \\\"filter_id\\\": 101, \\\"ai_rewrite\\\": true, \\\"model_name\\\": \\\"Stable Diffusion XL v1.0\\\", \\\"image_ratio\\\": \\\"1:1\\\", \\\"real_prompt\\\": \\\"A digital artwork depicting a heart made of translucent glass, standing on a pedestal amidst turbulent waves. A ray of sunlight penetrates through the clouds, illuminating the heart and revealing a miniature universe within. The horizon is engraved with a prominent line of text that reads \\'Find the universe within you\\'. The image should be (best quality, 4k, 8k, highres,1.2), ultra-detailed, realistic, with a vibrant color tone and dynamic lighting.\\\", \\\"real_negative_prompt\\\": \\\" nsfw, low quality, cropped, monochrome, lowres, low saturation, watermarks, white letters.\\\"}','这幅插画描绘了一颗由半透明玻璃制成的人心,矗立在惊涛骇浪中的基座上。一缕阳光穿透云层,照亮了心脏,揭示了其中的小宇宙。地平线上镌刻着一行醒目的大字 「Find the universe within you」。','','[\\\"https://ssl.aicode.cc/ai-server/24/20231022/aigca48f5ae9-9415-6e0d-bd95-3fa413af6043.png\\\"]',NULL,NULL,NULL,1,'2023-10-21 17:03:38','2023-11-29 03:00:01');", "INSERT INTO `creative_gallery` (`id`, `user_id`, `username`, `hot_value`, `creative_history_id`, `creative_type`, `meta`, `prompt`, `negative_prompt`, `answer`, `tags`, `ref_count`, `star_level`, `status`, `created_at`, `updated_at`) VALUES (627,24,'普罗米',38288,12266,2,'{\\\"mode\\\": \\\"canny\\\", \\\"steps\\\": 30, \\\"model_id\\\": \\\"stable-diffusion-xl-1024-v1-0\\\", \\\"filter_id\\\": 101, \\\"model_name\\\": \\\"Stable Diffusion XL v1.0\\\", \\\"image_ratio\\\": \\\"1:1\\\", \\\"real_negative_prompt\\\": \\\"out of frame, lowres, text, error, cropped, worst quality, low quality, jpeg artifacts, ugly, duplicate, morbid, mutilated, out of frame, extra fingers, mutated hands, poorly drawn hands, poorly drawn face, mutation, deformed, blurry, dehydrated, bad anatomy, bad proportions, extra limbs, cloned face, disfigured, gross proportions, malformed limbs, missing arms, missing legs, extra arms, extra legs, fused fingers, too many fingers, long neck, username, watermark, signature\\\"}','Dreaming of Mount Tianmu, crossing Mirror Lake at night, ascending the Cloudy Ladder at the residence of Lord Xie. Halfway up, the sea sun shines, the sky rooster crows, amidst thousands of twisting rocks and flowers. Bears roar, dragons chant, vibrating rocks and springs, terrifying the deep forest and peak. The sky darkens with clouds, the water grows calm, sudden thunder and lightning, mountains collapse. The heavenly cave opens with a thunderous sound','','[\\\"https://ssl.aicode.cc/ai-server/24/20231024/aigc3b8fe8a9-312b-60ff-7190-1ec9e6b1d07b.png\\\"]',NULL,NULL,NULL,1,'2023-10-24 04:06:44','2023-11-29 03:00:01');", + // 更新 creative_gallery_random 表,一定是放在最后的! + "INSERT INTO creative_gallery_random (gallery_id) SELECT id FROM creative_gallery", } }) diff --git a/pkg/repo/provider.go b/pkg/repo/provider.go index f574967..771e6e2 100644 --- a/pkg/repo/provider.go +++ b/pkg/repo/provider.go @@ -3,9 +3,11 @@ package repo import ( "database/sql" "errors" + "fmt" _ "github.com/go-sql-driver/mysql" "github.com/mylxsw/aidea-server/config" "github.com/mylxsw/asteria/log" + "time" "github.com/mylxsw/eloquent/event" "github.com/mylxsw/glacier/infra" @@ -41,7 +43,19 @@ func (Provider) Register(binder infra.Binder) { // MySQL 数据库连接 binder.MustSingleton(func(conf *config.Config) (*sql.DB, error) { - return sql.Open("mysql", conf.DBURI) + conn, err := sql.Open("mysql", conf.DBURI) + if err != nil { + // 第一次连接失败,等待 5 秒后重试 + // docker-compose 模式下,数据库可能还未完全初始化完成 + time.Sleep(time.Second * 5) + conn, err = sql.Open("mysql", conf.DBURI) + } + + if err != nil { + return nil, fmt.Errorf("数据库连接失败: %w", err) + } + + return conn, nil }) binder.MustSingleton(func(resolver infra.Resolver) *Repository { diff --git a/server/controllers/info.go b/server/controllers/info.go index 803aaed..16339f8 100644 --- a/server/controllers/info.go +++ b/server/controllers/info.go @@ -166,6 +166,8 @@ func (ctl *InfoController) Capabilities(ctx context.Context, webCtx web.Context, "support_websocket": ctl.conf.EnableWebsocket, // 是否支持 API Keys 配置 "support_api_keys": ctl.conf.EnableAPIKeys, + // 服务状态页 + "service_status_page": ctl.conf.ServiceStatusPage, }) } diff --git a/server/controllers/v2/creative-island.go b/server/controllers/v2/creative-island.go index 5457bf3..899c603 100644 --- a/server/controllers/v2/creative-island.go +++ b/server/controllers/v2/creative-island.go @@ -96,6 +96,11 @@ type CreativeIslandItem struct { Size string `json:"size,omitempty"` } +const ( + SizeLarge = "large" + SizeMedium = "medium" +) + func (ctl *CreativeIslandController) Items(ctx context.Context, webCtx web.Context, user *auth.UserOptional, client *auth.ClientInfo) web.Response { items := []CreativeIslandItem{ { @@ -104,7 +109,7 @@ func (ctl *CreativeIslandController) Items(ctx context.Context, webCtx web.Conte TitleColor: "FFFFFFFF", PreviewImage: "https://ssl.aicode.cc/ai-server/assets/background/image-text-to-image.jpeg-thumb1000", RouteURI: "/creative-draw/create?mode=text-to-image&id=text-to-image", - Size: "large", + Size: SizeLarge, }, } @@ -115,7 +120,7 @@ func (ctl *CreativeIslandController) Items(ctx context.Context, webCtx web.Conte TitleColor: "FFFFFFFF", PreviewImage: "https://ssl.aicode.cc/ai-server/assets/background/art-text-bg.jpg-thumb1000", RouteURI: "/creative-draw/artistic-text?type=text&id=artistic-text", - Size: "large", + Size: SizeLarge, }) items = append(items, CreativeIslandItem{ ID: "artistic-qr", @@ -123,7 +128,7 @@ func (ctl *CreativeIslandController) Items(ctx context.Context, webCtx web.Conte TitleColor: "FFFFFFFF", PreviewImage: "https://ssl.aicode.cc/ai-server/assets/background/art-qr-bg.jpg-thumb1000", RouteURI: "/creative-draw/artistic-text?type=qr&id=artistic-qr", - Size: "medium", + Size: SizeMedium, }) } @@ -134,7 +139,7 @@ func (ctl *CreativeIslandController) Items(ctx context.Context, webCtx web.Conte PreviewImage: "https://ssl.aicode.cc/ai-server/assets/background/image-image-to-image.jpeg-thumb1000", RouteURI: "/creative-draw/create?mode=image-to-image&id=image-to-image", Tag: ternary.If(client != nil && client.IsIOS(), "", "BETA"), - Size: "medium", + Size: SizeMedium, }) if client != nil && misc.VersionNewer(client.Version, "1.0.2") && ctl.conf.EnableDeepAI { @@ -145,7 +150,7 @@ func (ctl *CreativeIslandController) Items(ctx context.Context, webCtx web.Conte PreviewImage: "https://ssl.aicode.cc/ai-server/assets/background/super-res.jpeg-thumb1000", RouteURI: "/creative-draw/create-upscale", Note: "图片的高清修复功能能够把低分辨率的照片升级到高分辨率,让图片的清晰度得到明显提升。", - Size: "medium", + Size: SizeMedium, }) items = append(items, CreativeIslandItem{ @@ -155,7 +160,16 @@ func (ctl *CreativeIslandController) Items(ctx context.Context, webCtx web.Conte PreviewImage: "https://ssl.aicode.cc/ai-server/assets/background/image-colorizev2.jpeg-thumb1000", RouteURI: "/creative-draw/create-colorize", Note: "图片上色功能能够把黑白照片变成彩色照片,让照片的色彩更加丰富。", - Size: "medium", + Size: SizeMedium, + }) + } + + // 如果中等大小的项目不足 2 个,则把所有的项目都设置为大尺寸 + // TODO 临时处理 + if len(array.Filter(items, func(item CreativeIslandItem, _ int) bool { return item.Size == SizeMedium })) < 2 { + items = array.Map(items, func(item CreativeIslandItem, _ int) CreativeIslandItem { + item.Size = SizeLarge + return item }) }