diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..88137b31 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,98 @@ +variables: + PATH: /opt/go-1.21/go/bin/:/opt/node/node/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin + GOROOT: /opt/go-1.21/go + GOPROXY: https://goproxy.cn + VERSION: $CI_COMMIT_SHORT_SHA + APP: apipark + APP_PRE: ${APP}_${VERSION} + BUILD_DIR: ${APP}-build + DEPLOY_DESC: "DEV 环境" + VIEW_ADDR: http://172.18.166.219:8288 + SAVE_DIR: /opt/${APP} + NODE_OPTIONS: --max_old_space_size=8192 + +stages: + - notice + - prefix + - build + - deploy + - webhook + +feishu-informer: # 飞书回调 + stage: notice + variables: + DIFF_URL: "$CI_MERGE_REQUEST_PROJECT_URL/-/merge_requests/$CI_MERGE_REQUEST_IID/diffs" + rules: + - if: $CI_PIPELINE_SOURCE=="merge_request_event" && $CI_COMMIT_BRANCH =~ "main" + script: + - echo "merge request" + - | + curl -X POST -H "Content-Type: application/json" \ + -d "{\"msg_type\":\"text\",\"content\":{\"text\":\"项目:${CI_PROJECT_NAME}\\n提交人:${GITLAB_USER_NAME}\\n提交信息:${CI_MERGE_REQUEST_TITLE}\\n合并分支信息:${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME} -> ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}\\n差异性地址:${DIFF_URL}\\n请及时review代码\"}}" \ + https://open.feishu.cn/open-apis/bot/v2/hook/1c334752-2874-41a1-8f1b-3060f2d46b6c + +prebuild: + stage: prefix + rules: + - if: $CI_COMMIT_BRANCH == "main" + script: + - echo "prebuild" + - chmod +x ./scripts/prefix.sh + - ./scripts/prefix.sh + +builder: + stage: build + rules: + - if: $CI_COMMIT_BRANCH == "main" + script: + - set -e + - | + if [ ! -d "../artifacts" ]; then + mkdir -p ../artifacts + fi + if [ -d "../artifacts/dist" ]; then + cp -r ../artifacts/dist frontend/dist + fi + - | + if [ -n "$(git diff --name-status HEAD~1 HEAD -- frontend)" ]; then + ./scripts/build.sh $BUILD_DIR ${VERSION} all "" + else + ./scripts/build.sh $BUILD_DIR ${VERSION} + fi + if [ -d "frontend/dist" ]; then + echo "copy frontend/dist to artifacts/dist" + rm -fr ../artifacts/dist + cp -r frontend/dist ../artifacts/dist + fi + cp $BUILD_DIR/${APP_PRE}_linux_amd64.tar.gz ${SAVE_DIR} + +deployer: + stage: deploy + rules: + - if: $CI_COMMIT_BRANCH == "main" + variables: + APIPARK_GUEST_MODE: allow + APIPARK_GUEST_ID: dklejrfbhjqwdh + script: + - cd ${SAVE_DIR};mkdir -p ${APP_PRE};tar -zxvf ${APP_PRE}_linux_amd64.tar.gz -C ${APP_PRE};cd ${APP_PRE};./install.sh ${SAVE_DIR};./run.sh restart;cd ${SAVE_DIR} && ./clean.sh ${APP_PRE} + when: on_success +success: + stage: webhook + rules: + - if: $CI_COMMIT_BRANCH == "main" + script: + - | + curl -X POST -H "Content-Type: application/json" \ + -d "{\"msg_type\":\"text\",\"content\":{\"text\":\"最近一次提交:${CI_COMMIT_TITLE}\\n提交人:${GITLAB_USER_NAME}\\n项目:${CI_PROJECT_NAME}\\n环境:${DEPLOY_DESC}\\n更新部署完成.\\n访问地址:${VIEW_ADDR}\\n工作流地址:${CI_PIPELINE_URL}\"}}" \ + https://open.feishu.cn/open-apis/bot/v2/hook/c3672932-4dfa-4989-8023-0128bae59338 + when: on_success +failure: + stage: webhook + rules: + - if: $CI_COMMIT_BRANCH == "main" + script: + - | + curl -X POST -H "Content-Type: application/json" \ + -d "{\"msg_type\":\"text\",\"content\":{\"text\":\"最近一次提交:${CI_COMMIT_TITLE}\\n提交人:${GITLAB_USER_NAME}\\n项目:${CI_PROJECT_NAME}\\n环境:${DEPLOY_DESC}\\n更新部署失败,请及时到gitlab上查看\\n工作流地址:${CI_PIPELINE_URL}\"}}" \ + https://open.feishu.cn/open-apis/bot/v2/hook/c3672932-4dfa-4989-8023-0128bae59338 + when: on_failure diff --git a/app/ai-event-handler/.gitignore b/app/ai-event-handler/.gitignore new file mode 100644 index 00000000..a0ec5967 --- /dev/null +++ b/app/ai-event-handler/.gitignore @@ -0,0 +1 @@ +/config.yml diff --git a/app/ai-event-handler/main.go b/app/ai-event-handler/main.go new file mode 100644 index 00000000..f768a7d7 --- /dev/null +++ b/app/ai-event-handler/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "os/signal" + "syscall" + + "github.com/eolinker/go-common/autowire" + nsq "github.com/nsqio/go-nsq" + + "github.com/eolinker/go-common/cftool" + + _ "github.com/eolinker/go-common/store/store_mysql" + _ "github.com/go-sql-driver/mysql" +) + +var ( + version string + confPath string +) + +func init() { + flag.StringVar(&confPath, "c", "config.yml", "`config` file path for server ") +} + +type ServerConfig struct { + Port int `yaml:"port"` +} + +func main() { + // 1. 连接 MySQL 数据库 + cftool.Register[ServerConfig](fmt.Sprintf("root:%s", confPath)) + cftool.ReadFile(confPath) + + handler := &NSQHandler{} + autowire.Autowired(handler) + err := autowire.CheckComplete() + if err != nil { + log.Fatal("check autowired:", err) + return + } + // 2. 创建 NSQ 消费者 + config := nsq.NewConfig() + hostname, err := os.Hostname() + if err != nil { + log.Fatalf("Failed to get hostname: %v", err) + return + } + nsqConfig := handler.nsqConfig + consumer, err := nsq.NewConsumer(fmt.Sprintf("%s_ai_event", nsqConfig.TopicPrefix), hostname, config) + if err != nil { + log.Fatalf("Failed to create NSQ consumer: %v", err) + } + + consumer.AddHandler(handler) + + // 4. 连接到 NSQ + //nsqAddress := "172.18.166.219:9150" // NSQ 地址 + err = consumer.ConnectToNSQD(nsqConfig.Addr) + if err != nil { + log.Fatalf("Failed to connect to NSQ: %v", err) + } + log.Println("Connected to NSQ") + + // 5. 捕获系统信号,优雅关闭 + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + <-sigChan + + // 优雅停止消费者 + consumer.Stop() + <-consumer.StopChan + log.Println("NSQ Consumer stopped") +} diff --git a/app/ai-event-handler/nsq.go b/app/ai-event-handler/nsq.go new file mode 100644 index 00000000..f8e9e03c --- /dev/null +++ b/app/ai-event-handler/nsq.go @@ -0,0 +1,161 @@ +package main + +import ( + "context" + "encoding/json" + "log" + "strings" + "time" + + "github.com/eolinker/go-common/cftool" + + ai_dto "github.com/APIParkLab/APIPark/module/ai/dto" + + "github.com/eolinker/go-common/store" + + "github.com/APIParkLab/APIPark/service/ai" + + ai_key "github.com/APIParkLab/APIPark/service/ai-key" + + nsq "github.com/nsqio/go-nsq" + + ai_api "github.com/APIParkLab/APIPark/service/ai-api" +) + +func init() { + cftool.Register[NSQConfig]("nsq") +} + +type NSQConfig struct { + Addr string `json:"addr"` + TopicPrefix string `json:"topic_prefix"` +} + +// 定义 NSQ 消息结构 +type AIProviderStatus struct { + Provider string `json:"provider"` + Model string `json:"model"` + Key string `json:"key"` + Status string `json:"status"` +} + +type AIInfo struct { + Model string `json:"ai_model"` + Cost interface{} `json:"ai_model_cost"` + InputToken interface{} `json:"ai_model_input_token"` + OutputToken interface{} `json:"ai_model_output_token"` + TotalToken interface{} `json:"ai_model_total_token"` + Provider string `json:"ai_provider"` + ProviderStats []AIProviderStatus `json:"ai_provider_statuses"` +} + +type NSQMessage struct { + AI AIInfo `json:"ai"` + API string `json:"api"` + Provider string `json:"provider"` + RequestID string `json:"request_id"` + TimeISO8601 string `json:"time_iso8601"` +} + +// NSQHandler 处理 NSQ 消息并写入 MySQL +type NSQHandler struct { + apiUseService ai_api.IAPIUseService `autowired:""` + aiKeyService ai_key.IKeyService `autowired:""` + aiService ai.IProviderService `autowired:""` + transaction store.ITransaction `autowired:""` + nsqConfig *NSQConfig `autowired:""` + ctx context.Context +} + +func convertInt(value interface{}) int { + switch v := value.(type) { + case int: + return v + case float64: + return int(v) + default: + return 0 + } +} + +// HandleMessage 处理从 NSQ 读取的消息 +func (h *NSQHandler) HandleMessage(message *nsq.Message) error { + log.Printf("Received message: %s", string(message.Body)) + + // 解析消息为结构体 + var data NSQMessage + err := json.Unmarshal(message.Body, &data) + if err != nil { + log.Printf("Failed to unmarshal message: %v", err) + return err + } + + // 将时间字符串转换为 time.Time + timestamp, err := time.Parse(time.RFC3339, data.TimeISO8601) + if err != nil { + log.Printf("Failed to parse timestamp: %v", err) + return err + } + + day := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), 0, 0, 0, 0, timestamp.Location()) + hour := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), timestamp.Hour(), 0, 0, 0, timestamp.Location()) + minute := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), timestamp.Hour(), timestamp.Minute(), 0, 0, timestamp.Location()) + return h.transaction.Transaction(context.Background(), func(ctx context.Context) error { + finalStatus := &AIProviderStatus{} + for _, s := range data.AI.ProviderStats { + status := ToKeyStatus(s.Status).Int() + keys := strings.Split(s.Key, "@") + key := keys[0] + err = h.aiKeyService.Save(ctx, key, &ai_key.Edit{ + Status: &status, + }) + if err != nil { + log.Printf("Failed to save AI key: %v", err) + return err + } + if s.Provider != data.AI.Provider { + + pStatus := ai_dto.ProviderAbnormal.Int() + err = h.aiService.Save(ctx, s.Provider, &ai.SetProvider{ + Status: &pStatus, + }) + } else { + pStatus := ai_dto.ProviderEnabled.Int() + err = h.aiService.Save(ctx, s.Provider, &ai.SetProvider{ + Status: &pStatus, + }) + } + finalStatus = &s + } + if finalStatus != nil { + keys := strings.Split(finalStatus.Key, "@") + err = h.aiKeyService.IncrUseToken(ctx, keys[0], convertInt(data.AI.TotalToken)) + if err != nil { + log.Printf("Failed to increment AI key token: %v", err) + return err + } + } + + // 调用 AI API 接口 + err = h.apiUseService.Incr(context.Background(), &ai_api.IncrAPIUse{ + API: data.API, + Service: data.Provider, + Provider: data.AI.Provider, + Model: data.AI.Model, + Day: day.Unix(), + Hour: hour.Unix(), + Minute: minute.Unix(), + InputToken: convertInt(data.AI.InputToken), + OutputToken: convertInt(data.AI.OutputToken), + TotalToken: convertInt(data.AI.TotalToken), + }) + if err != nil { + log.Printf("Failed to call AI API: %v", err) + return err + } + + log.Printf("Message processed and saved to MySQL: %+v", data) + return nil + }) + +} diff --git a/app/ai-event-handler/status.go b/app/ai-event-handler/status.go new file mode 100644 index 00000000..9b1ff61d --- /dev/null +++ b/app/ai-event-handler/status.go @@ -0,0 +1,34 @@ +package main + +import ai_key_dto "github.com/APIParkLab/APIPark/module/ai-key/dto" + +var ( + StatusNormal = "normal" + StatusInvalidRequest = "invalid request" + StatusQuotaExhausted = "quota exhausted" + StatusExpired = "expired" + StatusExceeded = "exceeded" + StatusInvalid = "invalid" + StatusTimeout = "timeout" +) + +func ToKeyStatus(status string) ai_key_dto.KeyStatus { + switch status { + case StatusNormal: + return ai_key_dto.KeyNormal + case StatusInvalidRequest: + return ai_key_dto.KeyNormal + case StatusQuotaExhausted: + return ai_key_dto.KeyExceed + case StatusExpired: + return ai_key_dto.KeyExpired + case StatusExceeded: + return ai_key_dto.KeyNormal + case StatusInvalid: + return ai_key_dto.KeyError + case StatusTimeout: + return ai_key_dto.KeyError + default: + return ai_key_dto.KeyNormal + } +} diff --git a/controller/ai-api/iml.go b/controller/ai-api/iml.go index fc59cc5f..d66a77a3 100644 --- a/controller/ai-api/iml.go +++ b/controller/ai-api/iml.go @@ -2,7 +2,6 @@ package ai_api import ( "context" - "fmt" "net/http" "github.com/APIParkLab/APIPark/model/plugin_model" @@ -52,7 +51,7 @@ func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_ plugins["ai_formatter"] = api.PluginSetting{ Config: plugin_model.ConfigType{ "model": input.AiModel.Id, - "provider": fmt.Sprintf("%s@ai-provider", input.AiModel.Provider), + "provider": input.AiModel.Provider, "config": input.AiModel.Config, }, } @@ -73,8 +72,8 @@ func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_ Retry: input.Retry, Plugins: plugins, }, - Upstream: input.AiModel.Provider, - Disable: false, + //Upstream: input.AiModel.Provider, + Disable: false, }) return err @@ -101,16 +100,16 @@ func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string Retry: apiInfo.Proxy.Retry, Plugins: apiInfo.Proxy.Plugins, } - var upstream *string + //var upstream *string if input.AiModel != nil { proxy.Plugins["ai_formatter"] = api.PluginSetting{ Config: plugin_model.ConfigType{ "model": input.AiModel.Id, - "provider": fmt.Sprintf("%s@ai-provider", input.AiModel.Provider), + "provider": input.AiModel.Provider, "config": input.AiModel.Config, }, } - upstream = &input.AiModel.Provider + //upstream = &input.AiModel.Provider } if input.AiPrompt != nil { @@ -128,7 +127,7 @@ func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string Path: input.Path, Disable: input.Disable, Methods: &apiInfo.Methods, - Upstream: upstream, + //Upstream: upstream, }) if err != nil { return err diff --git a/controller/ai-key/controller.go b/controller/ai-key/controller.go index 1a8229d1..09bd5938 100644 --- a/controller/ai-key/controller.go +++ b/controller/ai-key/controller.go @@ -13,7 +13,7 @@ type IKeyController interface { Edit(ctx *gin.Context, providerId string, id string, input *ai_key_dto.Edit) error Delete(ctx *gin.Context, providerId string, id string) error Get(ctx *gin.Context, providerId string, id string) (*ai_key_dto.Key, error) - List(ctx *gin.Context, providerId string, keyword string, page string, pageSize string) ([]*ai_key_dto.Item, int64, error) + List(ctx *gin.Context, providerId string, keyword string, page string, pageSize string, statuses string) ([]*ai_key_dto.Item, int64, error) Enable(ctx *gin.Context, providerId string, id string) error Disable(ctx *gin.Context, providerId string, id string) error Sort(ctx *gin.Context, providerId string, input *ai_key_dto.Sort) error diff --git a/controller/ai-key/iml.go b/controller/ai-key/iml.go index 73ae6399..bf431333 100644 --- a/controller/ai-key/iml.go +++ b/controller/ai-key/iml.go @@ -1,6 +1,7 @@ package ai_key import ( + "encoding/json" "strconv" ai_key "github.com/APIParkLab/APIPark/module/ai-key" @@ -38,7 +39,7 @@ func (i *imlAIKeyController) Get(ctx *gin.Context, providerId string, id string) return i.module.Get(ctx, providerId, id) } -func (i *imlAIKeyController) List(ctx *gin.Context, providerId string, keyword string, page string, pageSize string) ([]*ai_key_dto.Item, int64, error) { +func (i *imlAIKeyController) List(ctx *gin.Context, providerId string, keyword string, page string, pageSize string, statuses string) ([]*ai_key_dto.Item, int64, error) { p, err := strconv.Atoi(page) if err != nil { if page != "" { @@ -51,9 +52,16 @@ func (i *imlAIKeyController) List(ctx *gin.Context, providerId string, keyword s if pageSize != "" { return nil, 0, err } - ps = 15 + ps = 20 } - return i.module.List(ctx, providerId, keyword, p, ps) + ss := make([]string, 0) + if statuses != "" { + err = json.Unmarshal([]byte(statuses), &ss) + if err != nil { + return nil, 0, err + } + } + return i.module.List(ctx, providerId, keyword, p, ps, ss) } func (i *imlAIKeyController) Sort(ctx *gin.Context, providerId string, input *ai_key_dto.Sort) error { diff --git a/controller/ai/controller.go b/controller/ai/controller.go index 5c9fed77..581425d9 100644 --- a/controller/ai/controller.go +++ b/controller/ai/controller.go @@ -3,17 +3,16 @@ package ai import ( "reflect" - "github.com/eolinker/go-common/auto" - ai_dto "github.com/APIParkLab/APIPark/module/ai/dto" "github.com/eolinker/go-common/autowire" "github.com/gin-gonic/gin" ) type IProviderController interface { - ConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ConfiguredProviderItem, *auto.Label, error) + ConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ConfiguredProviderItem, *ai_dto.BackupProvider, error) UnConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ProviderItem, error) SimpleProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, error) + SimpleConfiguredProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, *ai_dto.BackupProvider, error) Provider(ctx *gin.Context, id string) (*ai_dto.Provider, error) SimpleProvider(ctx *gin.Context, id string) (*ai_dto.SimpleProvider, error) LLMs(ctx *gin.Context, driver string) ([]*ai_dto.LLMItem, *ai_dto.ProviderItem, error) @@ -25,7 +24,7 @@ type IProviderController interface { } type IStatisticController interface { - APIs(ctx *gin.Context, keyword string, providerId string, start string, end string, page string, pageSize string, sortCondition string, asc string) ([]*ai_dto.APIItem, int64, error) + APIs(ctx *gin.Context, keyword string, providerId string, start string, end string, page string, pageSize string, sortCondition string, asc string, models string, services string) ([]*ai_dto.APIItem, *ai_dto.Condition, int64, error) } func init() { diff --git a/controller/ai/iml.go b/controller/ai/iml.go index dd9200e0..78b51087 100644 --- a/controller/ai/iml.go +++ b/controller/ai/iml.go @@ -1,11 +1,11 @@ package ai import ( + "encoding/json" "strconv" "github.com/APIParkLab/APIPark/module/ai" ai_dto "github.com/APIParkLab/APIPark/module/ai/dto" - "github.com/eolinker/go-common/auto" "github.com/gin-gonic/gin" ) @@ -21,7 +21,7 @@ func (i *imlProviderController) Sort(ctx *gin.Context, input *ai_dto.Sort) error return i.module.Sort(ctx, input) } -func (i *imlProviderController) ConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ConfiguredProviderItem, *auto.Label, error) { +func (i *imlProviderController) ConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ConfiguredProviderItem, *ai_dto.BackupProvider, error) { return i.module.ConfiguredProviders(ctx) } @@ -33,6 +33,10 @@ func (i *imlProviderController) SimpleProviders(ctx *gin.Context) ([]*ai_dto.Sim return i.module.SimpleProviders(ctx) } +func (i *imlProviderController) SimpleConfiguredProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, *ai_dto.BackupProvider, error) { + return i.module.SimpleConfiguredProviders(ctx) +} + func (i *imlProviderController) Provider(ctx *gin.Context, id string) (*ai_dto.Provider, error) { return i.module.Provider(ctx, id) } @@ -46,11 +50,13 @@ func (i *imlProviderController) LLMs(ctx *gin.Context, driver string) ([]*ai_dto } func (i *imlProviderController) Enable(ctx *gin.Context, id string) error { - return i.module.UpdateProviderStatus(ctx, id, true) + //return i.module.UpdateProviderStatus(ctx, id, true) + return nil } func (i *imlProviderController) Disable(ctx *gin.Context, id string) error { - return i.module.UpdateProviderStatus(ctx, id, false) + //return i.module.UpdateProviderStatus(ctx, id, false) + return nil } func (i *imlProviderController) UpdateProviderConfig(ctx *gin.Context, id string, input *ai_dto.UpdateConfig) error { @@ -58,7 +64,8 @@ func (i *imlProviderController) UpdateProviderConfig(ctx *gin.Context, id string } func (i *imlProviderController) UpdateProviderDefaultLLM(ctx *gin.Context, id string, input *ai_dto.UpdateLLM) error { - return i.module.UpdateProviderDefaultLLM(ctx, id, input) + //return i.module.UpdateProviderDefaultLLM(ctx, id, input) + return nil } var _ IStatisticController = (*imlStatisticController)(nil) @@ -67,21 +74,21 @@ type imlStatisticController struct { module ai.IAIAPIModule `autowired:""` } -func (i *imlStatisticController) APIs(ctx *gin.Context, keyword string, providerId string, start string, end string, page string, pageSize string, sortCondition string, asc string) ([]*ai_dto.APIItem, int64, error) { +func (i *imlStatisticController) APIs(ctx *gin.Context, keyword string, providerId string, start string, end string, page string, pageSize string, sortCondition string, asc string, models string, services string) ([]*ai_dto.APIItem, *ai_dto.Condition, int64, error) { s, err := strconv.ParseInt(start, 10, 64) if err != nil { - return nil, 0, err + return nil, nil, 0, err } e, err := strconv.ParseInt(end, 10, 64) if err != nil { - return nil, 0, err + return nil, nil, 0, err } p, err := strconv.Atoi(page) if err != nil { if page != "" { - return nil, 0, err + return nil, nil, 0, err } p = 1 } @@ -89,9 +96,19 @@ func (i *imlStatisticController) APIs(ctx *gin.Context, keyword string, provider ps, err := strconv.Atoi(pageSize) if err != nil { if pageSize != "" { - return nil, 0, err + return nil, nil, 0, err } - ps = 15 + ps = 20 } - return i.module.APIs(ctx, keyword, providerId, s, e, p, ps, sortCondition, asc == "true") + ms := make([]string, 0) + if models != "" { + json.Unmarshal([]byte(models), &ms) + ms = append(ms, models) + } + ss := make([]string, 0) + if services != "" { + json.Unmarshal([]byte(services), &ss) + ss = append(ss, services) + } + return i.module.APIs(ctx, keyword, providerId, s, e, p, ps, sortCondition, asc == "true", ms, ss) } diff --git a/controller/service/iml.go b/controller/service/iml.go index 7227e6b7..f75d66bf 100644 --- a/controller/service/iml.go +++ b/controller/service/iml.go @@ -318,8 +318,8 @@ func (i *imlServiceController) Get(ctx *gin.Context, id string) (*service_dto.Se return i.module.Get(ctx, id) } -func (i *imlServiceController) Search(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error) { - return i.module.Search(ctx, teamID, keyword) +func (i *imlServiceController) Search(ctx *gin.Context, teamIDs string, keyword string) ([]*service_dto.ServiceItem, error) { + return i.module.Search(ctx, teamIDs, keyword) } func (i *imlServiceController) Create(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error) { diff --git a/controller/service/service.go b/controller/service/service.go index 4e2a553e..c46ad21f 100644 --- a/controller/service/service.go +++ b/controller/service/service.go @@ -15,7 +15,7 @@ type IServiceController interface { Get(ctx *gin.Context, id string) (*service_dto.Service, error) // SearchMyServices 搜索服务 SearchMyServices(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error) - Search(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error) + Search(ctx *gin.Context, teamIDs string, keyword string) ([]*service_dto.ServiceItem, error) // Create 创建 Create(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error) // Edit 编辑 diff --git a/controller/system/iml.go b/controller/system/iml.go index 1b9c0434..f54e05b4 100644 --- a/controller/system/iml.go +++ b/controller/system/iml.go @@ -437,7 +437,7 @@ func (i *imlInitController) createAIService(ctx context.Context, teamID string, plugins["ai_formatter"] = api.PluginSetting{ Config: plugin_model.ConfigType{ "model": aiModel.Id, - "provider": fmt.Sprintf("%s@ai-provider", info.Provider.Id), + "provider": info.Provider.Id, "config": aiModel.Config, }, } @@ -457,8 +457,8 @@ func (i *imlInitController) createAIService(ctx context.Context, teamID string, Retry: retry, Plugins: plugins, }, - Disable: false, - Upstream: info.Provider.Id, + Disable: false, + //Upstream: info.Provider.Id, }) if err != nil { return err diff --git a/gateway/apinto/entity/router.go b/gateway/apinto/entity/router.go index 45406b28..664f0eb3 100644 --- a/gateway/apinto/entity/router.go +++ b/gateway/apinto/entity/router.go @@ -161,7 +161,7 @@ func ToRouter(r *gateway.ApiRelease, version string, matches map[string]string) labels = r.Labels } - return &Router{ + router := &Router{ BasicInfo: &BasicInfo{ ID: fmt.Sprintf("%s@router", r.ID), Name: r.ID, @@ -174,13 +174,16 @@ func ToRouter(r *gateway.ApiRelease, version string, matches map[string]string) Method: r.Methods, Location: r.Path, Rules: rules, - Service: fmt.Sprintf("%s@service", r.Service), Plugins: plugin, Retry: r.Retry, TimeOut: r.Timeout, Labels: labels, Protocols: []string{"http", "https"}, } + if r.Service != "" { + router.Service = fmt.Sprintf("%s@service", r.Service) + } + return router } // formatProxyPath 格式化转发路径上,用于转发重写插件正则替换 比如 请求路径 /path/{A}/{B} 原转发路径:/path/{B} 格式化后 新转发路径: /path/$2 diff --git a/gateway/profession.go b/gateway/profession.go index 606a4904..16956421 100644 --- a/gateway/profession.go +++ b/gateway/profession.go @@ -8,6 +8,7 @@ const ( ProfessionStrategy = "strategy" ProfessionService = "service" ProfessionAIProvider = "ai-provider" + ProfessionAIResource = "ai-resource" ) func RegisterDynamicResourceDriver(key string, worker Worker) { @@ -61,6 +62,14 @@ var dynamicResourceMap = map[string]Worker{ Profession: ProfessionOutput, Driver: "loki", }, + "ai-provider": { + Profession: ProfessionAIResource, + Driver: "ai-provider", + }, + "ai-key": { + Profession: ProfessionAIResource, + Driver: "ai-key", + }, } type Worker struct { diff --git a/go.mod b/go.mod index 6fdc3ad9..fb4396bf 100644 --- a/go.mod +++ b/go.mod @@ -7,12 +7,14 @@ go 1.21 require ( github.com/eolinker/ap-account v1.0.15 github.com/eolinker/eosc v0.18.3 - github.com/eolinker/go-common v1.1.3 + github.com/eolinker/go-common v1.1.4 github.com/gabriel-vasile/mimetype v1.4.4 github.com/getkin/kin-openapi v0.127.0 github.com/gin-gonic/gin v1.10.0 + github.com/go-sql-driver/mysql v1.7.0 github.com/google/uuid v1.6.0 github.com/influxdata/influxdb-client-go/v2 v2.14.0 + github.com/nsqio/go-nsq v1.1.0 github.com/urfave/cli v1.22.16 golang.org/x/crypto v0.24.0 gopkg.in/yaml.v3 v3.0.1 @@ -37,8 +39,8 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect - github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect diff --git a/go.sum b/go.sum index b1c0e925..01c9b855 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/eolinker/ap-account v1.0.15 h1:n6DJeL6RHZ8eLlZUcY2U3H4d/GPaA5oelAx3R0 github.com/eolinker/ap-account v1.0.15/go.mod h1:zm/Ivs6waJ/M/nEszhpPmM6g50y/MKO+5eABFAdeD0g= github.com/eolinker/eosc v0.18.3 h1:3IK5HkAPnJRfLbQ0FR7kWsZr6Y/OiqqGazvN1q2BL5A= github.com/eolinker/eosc v0.18.3/go.mod h1:O9PQQXFCpB6fjHf+oFt/LN6EOAv779ItbMixMKCfTfk= -github.com/eolinker/go-common v1.1.3 h1:Chb0x6sj0hZKpIN6Qo9IMdi9pX2KLNUPmqcaAD8mmWs= -github.com/eolinker/go-common v1.1.3/go.mod h1:Kb/jENMN1mApnodvRgV4YwO9FJby1Jkt2EUjrBjvSX4= +github.com/eolinker/go-common v1.1.4 h1:U5AtQMr3RCudgeV6jcX5TemUGrTz8WqLu//KrZm3BzA= +github.com/eolinker/go-common v1.1.4/go.mod h1:Kb/jENMN1mApnodvRgV4YwO9FJby1Jkt2EUjrBjvSX4= github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY= @@ -64,6 +64,9 @@ github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -107,6 +110,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/nsqio/go-nsq v1.1.0 h1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE= +github.com/nsqio/go-nsq v1.1.0/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY= github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo= github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= diff --git a/module/ai-api/dto/input.go b/module/ai-api/dto/input.go index be94417e..fe71acac 100644 --- a/module/ai-api/dto/input.go +++ b/module/ai-api/dto/input.go @@ -5,7 +5,7 @@ type CreateAPI struct { Name string `json:"name"` Path string `json:"path"` Description string `json:"description"` - Disable bool `json:"disable"` + Disable bool `json:"disabled"` AiPrompt *AiPrompt `json:"ai_prompt"` AiModel *AiModel `json:"ai_model"` Timeout int `json:"timeout"` @@ -33,7 +33,7 @@ type EditAPI struct { Name *string `json:"name"` Path *string `json:"path"` Description *string `json:"description"` - Disable *bool `json:"disable"` + Disable *bool `json:"disabled"` AiPrompt *AiPrompt `json:"ai_prompt"` AiModel *AiModel `json:"ai_model"` Timeout *int `json:"timeout"` diff --git a/module/ai-api/dto/output.go b/module/ai-api/dto/output.go index d8c8ade8..a00c6690 100644 --- a/module/ai-api/dto/output.go +++ b/module/ai-api/dto/output.go @@ -9,7 +9,7 @@ type API struct { Name string `json:"name"` Path string `json:"path"` Description string `json:"description"` - Disable bool `json:"disable"` + Disable bool `json:"disabled"` AiPrompt *AiPrompt `json:"ai_prompt"` AiModel *AiModel `json:"ai_model"` Timeout int `json:"timeout"` @@ -21,7 +21,7 @@ type APIItem struct { Name string `json:"name"` RequestPath string `json:"request_path"` Description string `json:"description"` - Disable bool `json:"disable"` + Disable bool `json:"disabled"` Creator auto.Label `json:"creator" aolabel:"user"` Updater auto.Label `json:"updater" aolabel:"user"` CreateTime auto.TimeLabel `json:"create_time"` @@ -30,19 +30,6 @@ type APIItem struct { Model ModelItem `json:"model"` } -type AIAPIItem struct { - Id string `json:"id"` - Name string `json:"name"` - Service auto.Label `json:"service" aolabel:"service"` - Method string `json:"method"` - RequestPath string `json:"request_path"` - Model ModelItem `json:"model"` - Provider ProviderItem `json:"provider"` - UpdateTime auto.TimeLabel `json:"update_time"` - UseToken int64 `json:"use_token"` - Disable bool `json:"disable"` -} - type ModelItem struct { Id string `json:"id"` Logo string `json:"logo"` diff --git a/module/ai-key/iml.go b/module/ai-key/iml.go index 7a8b3973..21cf216e 100644 --- a/module/ai-key/iml.go +++ b/module/ai-key/iml.go @@ -6,6 +6,13 @@ import ( "fmt" "time" + "github.com/APIParkLab/APIPark/service/cluster" + "github.com/eolinker/eosc/log" + + "github.com/APIParkLab/APIPark/gateway" + + "github.com/eolinker/go-common/utils" + "gorm.io/gorm" "github.com/eolinker/go-common/auto" @@ -25,9 +32,32 @@ import ( var _ IKeyModule = &imlKeyModule{} type imlKeyModule struct { - providerService ai.IProviderService `autowired:""` - aiKeyService ai_key.IKeyService `autowired:""` - transaction store.ITransaction `autowired:""` + providerService ai.IProviderService `autowired:""` + aiKeyService ai_key.IKeyService `autowired:""` + clusterService cluster.IClusterService `autowired:""` + transaction store.ITransaction `autowired:""` +} + +func newKey(key *ai_key.Key) *gateway.DynamicRelease { + + return &gateway.DynamicRelease{ + BasicItem: &gateway.BasicItem{ + ID: fmt.Sprintf("%s-%s", key.Provider, key.ID), + Description: key.Name, + Resource: "ai-key", + Version: time.Now().Format("20060102150405"), + MatchLabels: map[string]string{ + "module": "ai-key", + }, + }, + Attr: map[string]interface{}{ + "expired": key.ExpireTime, + "config": key.Config, + "provider": key.Provider, + "priority": key.Priority, + "disabled": key.Status == 0, + }, + } } func (i *imlKeyModule) Create(ctx context.Context, providerId string, input *ai_key_dto.Create) error { @@ -39,6 +69,7 @@ func (i *imlKeyModule) Create(ctx context.Context, providerId string, input *ai_ if !has { return fmt.Errorf("provider not found: %w", err) } + p.URI() err = p.Check(input.Config) if err != nil { return fmt.Errorf("config check failed: %w", err) @@ -54,11 +85,9 @@ func (i *imlKeyModule) Create(ctx context.Context, providerId string, input *ai_ status := ai_key_dto.KeyNormal.Int() if input.ExpireTime > 0 && time.Unix(int64(input.ExpireTime), 0).Before(time.Now()) { status = ai_key_dto.KeyExpired.Int() - } else { - // TODO: 发布Key到网关 } - return i.aiKeyService.Create(ctx, &ai_key.Create{ + err = i.aiKeyService.Create(ctx, &ai_key.Create{ ID: input.Id, Name: input.Name, Config: input.Config, @@ -67,9 +96,43 @@ func (i *imlKeyModule) Create(ctx context.Context, providerId string, input *ai_ ExpireTime: input.ExpireTime, Priority: priority + 1, }) + + info, _ := i.aiKeyService.Get(ctx, input.Id) + releases := []*gateway.DynamicRelease{newKey(info)} + return i.syncGateway(ctx, cluster.DefaultClusterID, releases, true) }) } +func (i *imlKeyModule) syncGateway(ctx context.Context, clusterId string, releases []*gateway.DynamicRelease, online bool) error { + client, err := i.clusterService.GatewayClient(ctx, clusterId) + if err != nil { + log.Errorf("get apinto client error: %v", err) + return nil + } + defer func() { + err := client.Close(ctx) + if err != nil { + log.Warn("close apinto client:", err) + } + }() + for _, releaseInfo := range releases { + dynamicClient, err := client.Dynamic(releaseInfo.Resource) + if err != nil { + return err + } + if online { + err = dynamicClient.Online(ctx, releaseInfo) + } else { + err = dynamicClient.Offline(ctx, releaseInfo) + } + if err != nil { + return err + } + } + + return nil +} + func (i *imlKeyModule) Edit(ctx context.Context, providerId string, id string, input *ai_key_dto.Edit) error { p, has := model_runtime.GetProvider(providerId) if !has { @@ -89,7 +152,7 @@ func (i *imlKeyModule) Edit(ctx context.Context, providerId string, id string, i if err != nil { return fmt.Errorf("config check failed: %w", err) } - cfg, err := p.GenConfig(info.Config, *input.Config) + cfg, err := p.GenConfig(*input.Config, info.Config) if err != nil { return fmt.Errorf("config gen failed: %w", err) } @@ -121,16 +184,25 @@ func (i *imlKeyModule) Edit(ctx context.Context, providerId string, id string, i // 停用、超额需要启用,所以维持原状态 status = orgStatus.Int() } - if status == ai_key_dto.KeyNormal.Int() { - // TODO: 发布Key到网关 - } - return i.aiKeyService.Save(ctx, id, &ai_key.Edit{ + err = i.aiKeyService.Save(ctx, id, &ai_key.Edit{ Name: input.Name, Config: input.Config, ExpireTime: input.ExpireTime, Status: &status, }) + if err != nil { + return err + } + if status == ai_key_dto.KeyNormal.Int() { + info, err = i.aiKeyService.Get(ctx, id) + if err != nil { + return err + } + releases := []*gateway.DynamicRelease{newKey(info)} + return i.syncGateway(ctx, cluster.DefaultClusterID, releases, true) + } + return nil }) } @@ -165,8 +237,18 @@ func (i *imlKeyModule) Delete(ctx context.Context, providerId string, id string) } } - // TODO: 操作网关下线Key - return i.aiKeyService.Delete(ctx, id) + err = i.aiKeyService.Delete(ctx, id) + if err != nil { + return err + } + return i.syncGateway(ctx, cluster.DefaultClusterID, []*gateway.DynamicRelease{{ + BasicItem: &gateway.BasicItem{ + ID: fmt.Sprintf("%s-%s", providerId, id), + Resource: "ai-key", + }, + Attr: nil, + }, + }, false) }) } @@ -192,7 +274,7 @@ func (i *imlKeyModule) Get(ctx context.Context, providerId string, id string) (* }, nil } -func (i *imlKeyModule) List(ctx context.Context, providerId string, keyword string, page, pageSize int) ([]*ai_key_dto.Item, int64, error) { +func (i *imlKeyModule) List(ctx context.Context, providerId string, keyword string, page, pageSize int, statuses []string) ([]*ai_key_dto.Item, int64, error) { _, err := i.aiKeyService.DefaultKey(ctx, providerId) if err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { @@ -217,12 +299,49 @@ func (i *imlKeyModule) List(ctx context.Context, providerId string, keyword stri return nil, 0, fmt.Errorf("create default key failed: %w", err) } } - list, total, err := i.aiKeyService.SearchByPage(ctx, keyword, map[string]interface{}{ + w := map[string]interface{}{ "provider": providerId, - }, page, pageSize, "sort asc") - if err != nil { - return nil, 0, err } + hasExpired := true + if len(statuses) > 0 { + hasExpired = false + statusConditions := make([]int, 0, len(statuses)) + for _, s := range statuses { + status := ai_key_dto.KeyStatus(s) + if status == ai_key_dto.KeyExpired { + hasExpired = true + } + statusConditions = append(statusConditions, status.Int()) + } + w["status"] = statusConditions + } + var list []*ai_key.Key + var total int64 + if !hasExpired { + if keyword != "" { + list, err = i.aiKeyService.Search(ctx, keyword, w, "sort asc") + if err != nil { + return nil, 0, err + } + if len(list) == 0 { + return nil, 0, nil + } + uuids := utils.SliceToSlice(list, func(key *ai_key.Key) string { + return key.ID + }) + w["uuid"] = uuids + } + list, total, err = i.aiKeyService.SearchUnExpiredByPage(ctx, w, page, pageSize, "sort asc") + if err != nil { + return nil, 0, err + } + } else { + list, total, err = i.aiKeyService.SearchByPage(ctx, keyword, w, page, pageSize, "sort asc") + if err != nil { + return nil, 0, err + } + } + var result []*ai_key_dto.Item for _, item := range list { status := ai_key_dto.ToKeyStatus(item.Status) @@ -255,11 +374,21 @@ func (i *imlKeyModule) UpdateKeyStatus(ctx context.Context, providerId string, i } return i.transaction.Transaction(ctx, func(ctx context.Context) error { if !enable { - // TODO:下线Key status := ai_key_dto.KeyDisable.Int() - return i.aiKeyService.Save(ctx, id, &ai_key.Edit{ + err = i.aiKeyService.Save(ctx, id, &ai_key.Edit{ Status: &status, }) + if err != nil { + return err + } + releases := []*gateway.DynamicRelease{{ + BasicItem: &gateway.BasicItem{ + ID: id, + Resource: "ai-key", + }, + Attr: nil, + }} + return i.syncGateway(ctx, providerId, releases, false) } if info.Status == ai_key_dto.KeyDisable.Int() || info.Status == ai_key_dto.KeyExceed.Int() { // 超额 或 停用状态,可启用 @@ -270,11 +399,19 @@ func (i *imlKeyModule) UpdateKeyStatus(ctx context.Context, providerId string, i Status: &status, }) } - // TODO:发布Key到网关 status := ai_key_dto.KeyNormal.Int() - return i.aiKeyService.Save(ctx, id, &ai_key.Edit{ + err = i.aiKeyService.Save(ctx, id, &ai_key.Edit{ Status: &status, }) + if err != nil { + return err + } + info, err = i.aiKeyService.Get(ctx, id) + if err != nil { + return err + } + releases := []*gateway.DynamicRelease{newKey(info)} + return i.syncGateway(ctx, providerId, releases, true) } return nil }) @@ -297,8 +434,14 @@ func (i *imlKeyModule) Sort(ctx context.Context, providerId string, input *ai_ke if err != nil { return err } - // TODO: 全量更新key配置到网关 - - return nil + list, err := i.aiKeyService.KeysByProvider(ctx, providerId) + if err != nil { + return err + } + releases := make([]*gateway.DynamicRelease, 0, len(list)) + for _, info := range list { + releases = append(releases, newKey(info)) + } + return i.syncGateway(ctx, cluster.DefaultClusterID, releases, true) }) } diff --git a/module/ai-key/module.go b/module/ai-key/module.go index 8a97bf51..30ddebbb 100644 --- a/module/ai-key/module.go +++ b/module/ai-key/module.go @@ -13,7 +13,7 @@ type IKeyModule interface { Edit(ctx context.Context, providerId string, id string, input *ai_key_dto.Edit) error Delete(ctx context.Context, providerId string, id string) error Get(ctx context.Context, providerId string, id string) (*ai_key_dto.Key, error) - List(ctx context.Context, providerId string, keyword string, page, pageSize int) ([]*ai_key_dto.Item, int64, error) + List(ctx context.Context, providerId string, keyword string, page, pageSize int, statuses []string) ([]*ai_key_dto.Item, int64, error) UpdateKeyStatus(ctx context.Context, providerId string, id string, enable bool) error Sort(ctx context.Context, providerId string, input *ai_key_dto.Sort) error } diff --git a/module/ai/dto/enum.go b/module/ai/dto/enum.go index 3c1b96a4..2cc76b6f 100644 --- a/module/ai/dto/enum.go +++ b/module/ai/dto/enum.go @@ -13,9 +13,9 @@ func (p ProviderStatus) Int() int { case ProviderAbnormal: return 2 case ProviderEnabled: - return 0 - case ProviderDisabled: return 1 + case ProviderDisabled: + return 0 default: return 1 } @@ -30,10 +30,10 @@ func ToProviderStatus(status int) ProviderStatus { case 2: return ProviderAbnormal case 0: - return ProviderEnabled + return ProviderDisabled case 1: - return ProviderDisabled + return ProviderEnabled default: - return ProviderDisabled + return ProviderEnabled } } diff --git a/module/ai/dto/output.go b/module/ai/dto/output.go index 137a4b61..83914ae6 100644 --- a/module/ai/dto/output.go +++ b/module/ai/dto/output.go @@ -5,10 +5,11 @@ import ( ) type SimpleProvider struct { - Id string `json:"id"` - Name string `json:"name"` - Logo string `json:"logo"` - GetAPIKeyUrl string `json:"get_apikey_url"` + Id string `json:"id"` + Name string `json:"name"` + DefaultConfig string `json:"default_config"` + Logo string `json:"logo"` + GetAPIKeyUrl string `json:"get_apikey_url"` } type Provider struct { @@ -16,10 +17,11 @@ type Provider struct { Name string `json:"name"` Config string `json:"config"` GetAPIKeyUrl string `json:"get_apikey_url"` - DefaultLLM string `json:"defaultLLM"` + DefaultLLM string `json:"default_llm"` DefaultLLMConfig string `json:"-"` Priority int `json:"priority"` Status ProviderStatus `json:"status"` + Configured bool `json:"configured"` } type ConfiguredProviderItem struct { @@ -30,14 +32,15 @@ type ConfiguredProviderItem struct { Status ProviderStatus `json:"status"` APICount int64 `json:"api_count"` KeyCount int `json:"key_count"` - KeyStatus []*KeyStatus `json:"key_status"` + KeyStatus []*KeyStatus `json:"keys"` Priority int `json:"priority"` } type KeyStatus struct { - Id string `json:"id"` - Name string `json:"name"` - Status string `json:"status"` + Id string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Priority int `json:"-"` } type ProviderItem struct { @@ -49,11 +52,20 @@ type ProviderItem struct { } type SimpleProviderItem struct { - Id string `json:"id"` - Name string `json:"name"` - Logo string `json:"logo"` - Configured bool `json:"configured"` - Status ProviderStatus `json:"status"` + Id string `json:"id"` + Name string `json:"name"` + Logo string `json:"logo"` + Configured bool `json:"configured"` + DefaultConfig string `json:"default_config"` + Status ProviderStatus `json:"status"` + Model *BasicInfo `json:"model,omitempty"` + Priority int `json:"-"` +} + +type BackupProvider struct { + Id string `json:"id"` + Name string `json:"name"` + Model *BasicInfo `json:"model,omitempty"` } type LLMItem struct { @@ -67,6 +79,7 @@ type APIItem struct { Id string `json:"id"` Name string `json:"name"` Service auto.Label `json:"service" aolabel:"service"` + Team auto.Label `json:"team" aolabel:"team"` Method string `json:"method"` RequestPath string `json:"request_path"` Model auto.Label `json:"model"` @@ -74,3 +87,13 @@ type APIItem struct { UseToken int `json:"use_token"` Disable bool `json:"disable"` } + +type Condition struct { + Models []*BasicInfo `json:"models"` + Services []*BasicInfo `json:"services"` +} + +type BasicInfo struct { + Id string `json:"id"` + Name string `json:"name"` +} diff --git a/module/ai/iml.go b/module/ai/iml.go index ac336a77..0e4b9a50 100644 --- a/module/ai/iml.go +++ b/module/ai/iml.go @@ -2,13 +2,14 @@ package ai import ( "context" - "encoding/json" "errors" "fmt" "net/http" "sort" "time" + "github.com/APIParkLab/APIPark/service/service" + ai_key_dto "github.com/APIParkLab/APIPark/module/ai-key/dto" ai_key "github.com/APIParkLab/APIPark/service/ai-key" @@ -28,24 +29,24 @@ import ( "gorm.io/gorm" ) -func newAIUpstream(provider string, uri model_runtime.IProviderURI) *gateway.DynamicRelease { +func newKey(key *ai_key.Key) *gateway.DynamicRelease { + return &gateway.DynamicRelease{ BasicItem: &gateway.BasicItem{ - ID: provider, - Description: fmt.Sprintf("auto create by ai provider %s", provider), - Resource: "service", + ID: fmt.Sprintf("%s-%s", key.Provider, key.ID), + Description: key.Name, + Resource: "ai-key", Version: time.Now().Format("20060102150405"), MatchLabels: map[string]string{ - "module": "service", + "module": "ai-key", }, }, Attr: map[string]interface{}{ - "driver": "http", - "balance": "round-robin", - "nodes": []string{fmt.Sprintf("%s weight=100", uri.Host())}, - "pass_host": "node", - "scheme": uri.Scheme(), - "timeout": 300000, + "expired": key.ExpireTime, + "config": key.Config, + "provider": key.Provider, + "priority": key.Priority, + "disabled": key.Status == 0, }, } } @@ -66,10 +67,11 @@ func (i *imlProviderModule) SimpleProvider(ctx context.Context, id string) (*ai_ return nil, fmt.Errorf("ai provider not found") } return &ai_dto.SimpleProvider{ - Id: p.ID(), - Name: p.Name(), - Logo: p.Logo(), - GetAPIKeyUrl: p.HelpUrl(), + Id: p.ID(), + Name: p.Name(), + Logo: p.Logo(), + DefaultConfig: p.DefaultConfig(), + GetAPIKeyUrl: p.HelpUrl(), }, nil } @@ -82,8 +84,19 @@ func (i *imlProviderModule) Sort(ctx context.Context, input *ai_dto.Sort) error providerMap := utils.SliceToMap(list, func(e *ai.Provider) string { return e.Id }) + releases := make([]*gateway.DynamicRelease, 0, len(list)) + offlineReleases := make([]*gateway.DynamicRelease, 0, len(list)) for index, id := range input.Providers { - _, has := providerMap[id] + p, has := model_runtime.GetProvider(id) + if !has { + continue + } + + l, has := providerMap[id] + if !has { + continue + } + model, has := p.GetModel(l.DefaultLLM) if !has { continue } @@ -94,12 +107,43 @@ func (i *imlProviderModule) Sort(ctx context.Context, input *ai_dto.Sort) error if err != nil { return err } + if ai_dto.ToProviderStatus(l.Status) == ai_dto.ProviderDisabled { + offlineReleases = append(offlineReleases, &gateway.DynamicRelease{ + BasicItem: &gateway.BasicItem{ + ID: l.Id, + Resource: "ai-provider", + }}) + } else { + cfg := make(map[string]interface{}) + cfg["provider"] = l.Id + cfg["model"] = l.DefaultLLM + cfg["model_config"] = model.DefaultConfig() + cfg["priority"] = l.Priority + cfg["base"] = fmt.Sprintf("%s://%s", p.URI().Scheme(), p.URI().Host()) + releases = append(releases, &gateway.DynamicRelease{ + BasicItem: &gateway.BasicItem{ + ID: l.Id, + Description: l.Name, + Resource: "ai-provider", + Version: l.UpdateAt.Format("20060102150405"), + MatchLabels: map[string]string{ + "module": "ai-provider", + }, + }, + Attr: cfg, + }) + } } - return nil + err = i.syncGateway(ctx, cluster.DefaultClusterID, releases, true) + if err != nil { + return err + } + return i.syncGateway(ctx, cluster.DefaultClusterID, offlineReleases, false) + }) } -func (i *imlProviderModule) ConfiguredProviders(ctx context.Context) ([]*ai_dto.ConfiguredProviderItem, *auto.Label, error) { +func (i *imlProviderModule) ConfiguredProviders(ctx context.Context) ([]*ai_dto.ConfiguredProviderItem, *ai_dto.BackupProvider, error) { // 获取已配置的AI服务商 list, err := i.providerService.List(ctx) if err != nil { @@ -151,11 +195,15 @@ func (i *imlProviderModule) ConfiguredProviders(ctx context.Context) ([]*ai_dto. status = ai_key_dto.KeyError } keysStatus = append(keysStatus, &ai_dto.KeyStatus{ - Id: k.ID, - Name: k.Name, - Status: status.String(), + Id: k.ID, + Name: k.Name, + Status: status.String(), + Priority: k.Priority, }) } + sort.Slice(keysStatus, func(i, j int) bool { + return keysStatus[i].Priority < keysStatus[j].Priority + }) providers = append(providers, &ai_dto.ConfiguredProviderItem{ Id: l.Id, @@ -181,10 +229,10 @@ func (i *imlProviderModule) ConfiguredProviders(ctx context.Context) ([]*ai_dto. } return providers[i].Name < providers[j].Name }) - var backup *auto.Label + var backup *ai_dto.BackupProvider for _, p := range providers { if p.Status == ai_dto.ProviderEnabled { - backup = &auto.Label{ + backup = &ai_dto.BackupProvider{ Id: p.Id, Name: p.Name, } @@ -207,20 +255,94 @@ func (i *imlProviderModule) SimpleProviders(ctx context.Context) ([]*ai_dto.Simp items := make([]*ai_dto.SimpleProviderItem, 0, len(providers)) for _, v := range providers { item := &ai_dto.SimpleProviderItem{ - Id: v.ID(), - Name: v.Name(), - Logo: v.Logo(), - Status: ai_dto.ProviderDisabled, + Id: v.ID(), + Name: v.Name(), + Logo: v.Logo(), + DefaultConfig: v.DefaultConfig(), + Status: ai_dto.ProviderDisabled, } if info, has := providerMap[v.ID()]; has { item.Configured = true item.Status = ai_dto.ToProviderStatus(info.Status) + item.Priority = info.Priority } items = append(items, item) } + sort.Slice(items, func(i, j int) bool { + if items[i].Priority != items[j].Priority { + if items[i].Priority == 0 { + return false + } + if items[j].Priority == 0 { + return true + } + return items[i].Priority < items[j].Priority + } + return items[i].Name < items[j].Name + }) return items, nil } +func (i *imlProviderModule) SimpleConfiguredProviders(ctx context.Context) ([]*ai_dto.SimpleProviderItem, *ai_dto.BackupProvider, error) { + list, err := i.providerService.List(ctx) + if err != nil { + return nil, nil, err + } + items := make([]*ai_dto.SimpleProviderItem, 0, len(list)) + var backup *ai_dto.BackupProvider + for _, l := range list { + p, has := model_runtime.GetProvider(l.Id) + if !has { + continue + } + model, has := p.GetModel(l.DefaultLLM) + if !has { + model, has = p.DefaultModel(model_runtime.ModelTypeLLM) + if !has { + continue + } + } + item := &ai_dto.SimpleProviderItem{ + Id: l.Id, + Name: l.Name, + Logo: p.Logo(), + DefaultConfig: p.DefaultConfig(), + Status: ai_dto.ToProviderStatus(l.Status), + Priority: l.Priority, + Configured: true, + Model: &ai_dto.BasicInfo{ + Id: model.ID(), + Name: model.ID(), + }, + } + + items = append(items, item) + } + sort.Slice(items, func(i, j int) bool { + if items[i].Priority != items[j].Priority { + if items[i].Priority == 0 { + return false + } + if items[j].Priority == 0 { + return true + } + return items[i].Priority < items[j].Priority + } + return items[i].Name < items[j].Name + }) + for _, item := range items { + if item.Status == ai_dto.ProviderEnabled { + backup = &ai_dto.BackupProvider{ + Id: item.Id, + Name: item.Name, + Model: item.Model, + } + break + } + } + return items, backup, nil +} + func (i *imlProviderModule) UnConfiguredProviders(ctx context.Context) ([]*ai_dto.ProviderItem, error) { list, err := i.providerService.List(ctx) if err != nil { @@ -313,7 +435,8 @@ func (i *imlProviderModule) Provider(ctx context.Context, id string) (*ai_dto.Pr DefaultLLM: defaultLLM.ID(), DefaultLLMConfig: defaultLLM.DefaultConfig(), Priority: info.Priority, - Status: ai_dto.ProviderEnabled, + Status: ai_dto.ToProviderStatus(info.Status), + Configured: true, }, nil } @@ -364,72 +487,6 @@ func (i *imlProviderModule) LLMs(ctx context.Context, driver string) ([]*ai_dto. }, nil } -func (i *imlProviderModule) UpdateProviderStatus(ctx context.Context, id string, enable bool) error { - driver, has := model_runtime.GetProvider(id) - if !has { - return fmt.Errorf("ai provider not found") - } - info, err := i.providerService.Get(ctx, id) - if err != nil { - if !errors.Is(err, gorm.ErrRecordNotFound) { - return err - } - return fmt.Errorf("ai provider not found") - } - - return i.transaction.Transaction(ctx, func(txCtx context.Context) error { - status := 0 - if enable { - status = 1 - } - err = i.providerService.Save(txCtx, id, &ai.SetProvider{ - Status: &status, - }) - if err != nil { - return err - } - if enable { - cfg := make(map[string]interface{}) - err = json.Unmarshal([]byte(info.Config), &cfg) - if err != nil { - log.Errorf("unmarshal ai provider config error,id is %s,err is %v", info.Id, err) - return err - } - cfg["driver"] = info.Id - - return i.syncGateway(txCtx, cluster.DefaultClusterID, []*gateway.DynamicRelease{{ - BasicItem: &gateway.BasicItem{ - ID: info.Id, - Description: info.Name, - Version: info.UpdateAt.Format("20060102150405"), - MatchLabels: map[string]string{ - "module": "ai-provider", - }, - }, - Attr: cfg, - }, newAIUpstream(info.Id, driver.URI()), - }, enable) - } else { - return i.syncGateway(txCtx, cluster.DefaultClusterID, []*gateway.DynamicRelease{ - { - BasicItem: &gateway.BasicItem{ - ID: info.Id, - Resource: info.Id, - }, - }, - { - BasicItem: &gateway.BasicItem{ - ID: info.Id, - Resource: "service", - }, - }, - }, enable) - } - - }) - -} - func (i *imlProviderModule) UpdateProviderConfig(ctx context.Context, id string, input *ai_dto.UpdateConfig) error { p, has := model_runtime.GetProvider(id) if !has { @@ -454,6 +511,10 @@ func (i *imlProviderModule) UpdateProviderConfig(ctx context.Context, id string, Config: input.Config, } } + model, has := p.GetModel(input.DefaultLLM) + if !has { + return fmt.Errorf("ai provider model not found") + } err = p.Check(input.Config) if err != nil { return err @@ -486,6 +547,7 @@ func (i *imlProviderModule) UpdateProviderConfig(ctx context.Context, id string, Status: 1, ExpireTime: 0, Default: true, + Priority: 1, }) } else { err = i.aiKeyService.Save(ctx, id, &ai_key.Edit{ @@ -495,6 +557,7 @@ func (i *imlProviderModule) UpdateProviderConfig(ctx context.Context, id string, if err != nil { return err } + if input.Enable != nil { status = 0 if *input.Enable { @@ -506,62 +569,73 @@ func (i *imlProviderModule) UpdateProviderConfig(ctx context.Context, id string, if err != nil { return err } - cfg := make(map[string]interface{}) - err = json.Unmarshal([]byte(input.Config), &cfg) + if *pInfo.Status == 0 { + return i.syncGateway(ctx, cluster.DefaultClusterID, []*gateway.DynamicRelease{ + { + BasicItem: &gateway.BasicItem{ + ID: id, + Resource: "ai-provider", + }, + }, + }, false) + } + // 获取当前供应商所有Key信息 + defaultKey, err := i.aiKeyService.DefaultKey(ctx, id) if err != nil { - log.Errorf("unmarshal ai provider config error,id is %s,err is %v", id, err) return err } - + cfg := make(map[string]interface{}) + cfg["provider"] = info.Id + cfg["model"] = info.DefaultLLM + cfg["model_config"] = model.DefaultConfig() + cfg["priority"] = info.Priority + cfg["base"] = fmt.Sprintf("%s://%s", p.URI().Scheme(), p.URI().Host()) return i.syncGateway(ctx, cluster.DefaultClusterID, []*gateway.DynamicRelease{ { BasicItem: &gateway.BasicItem{ ID: id, Description: info.Name, - Resource: id, + Resource: "ai-provider", Version: info.UpdateAt.Format("20060102150405"), MatchLabels: map[string]string{ "module": "ai-provider", }, }, Attr: cfg, - }, newAIUpstream(id, p.URI()), + }, newKey(defaultKey), }, true) }) } -func (i *imlProviderModule) UpdateProviderDefaultLLM(ctx context.Context, id string, input *ai_dto.UpdateLLM) error { - _, err := i.providerService.Get(ctx, id) - if err != nil { - if !errors.Is(err, gorm.ErrRecordNotFound) { - return err - } - return fmt.Errorf("ai provider not found") - } - return i.providerService.Save(ctx, id, &ai.SetProvider{ - DefaultLLM: &input.LLM, - }) -} - func (i *imlProviderModule) getAiProviders(ctx context.Context) ([]*gateway.DynamicRelease, error) { list, err := i.providerService.List(ctx) if err != nil { return nil, err } + providers := make([]*gateway.DynamicRelease, 0, len(list)) - for _, p := range list { - cfg := make(map[string]interface{}) - err = json.Unmarshal([]byte(p.Config), &cfg) - if err != nil { - log.Errorf("unmarshal ai provider config error,id is %s,err is %v", p.Id, err) - continue + for _, l := range list { + // 获取当前供应商所有Key信息 + + driver, has := model_runtime.GetProvider(l.Id) + if !has { + return nil, fmt.Errorf("provider not found: %s", l.Id) } + model, has := driver.GetModel(l.DefaultLLM) + if !has { + return nil, fmt.Errorf("model not found: %s", l.DefaultLLM) + } + cfg := make(map[string]interface{}) + cfg["provider"] = l.Id + cfg["model"] = l.DefaultLLM + cfg["model_config"] = model.DefaultConfig() + cfg["priority"] = l.Priority providers = append(providers, &gateway.DynamicRelease{ BasicItem: &gateway.BasicItem{ - ID: p.Id, - Description: p.Name, - Resource: p.Id, - Version: p.UpdateAt.Format("20060102150405"), + ID: l.Id, + Description: l.Name, + Resource: "ai-provider", + Version: l.UpdateAt.Format("20060102150405"), MatchLabels: map[string]string{ "module": "ai-provider", }, @@ -577,16 +651,9 @@ func (i *imlProviderModule) initGateway(ctx context.Context, clusterId string, c if err != nil { return err } - serviceClient, err := clientDriver.Dynamic("service") - if err != nil { - return err - } + for _, p := range providers { - driver, has := model_runtime.GetProvider(p.ID) - if !has { - continue - } - client, err := clientDriver.Dynamic(p.ID) + client, err := clientDriver.Dynamic(p.Resource) if err != nil { return err } @@ -594,12 +661,6 @@ func (i *imlProviderModule) initGateway(ctx context.Context, clusterId string, c if err != nil { return err } - - err = serviceClient.Online(ctx, newAIUpstream(p.ID, driver.URI())) - if err != nil { - return err - } - } return nil @@ -625,7 +686,7 @@ func (i *imlProviderModule) syncGateway(ctx context.Context, clusterId string, r if online { err = dynamicClient.Online(ctx, releaseInfo) } else { - err = dynamicClient.Offline(ctx, releaseInfo) + dynamicClient.Offline(ctx, releaseInfo) } if err != nil { return err @@ -638,26 +699,60 @@ func (i *imlProviderModule) syncGateway(ctx context.Context, clusterId string, r var _ IAIAPIModule = (*imlAIApiModule)(nil) type imlAIApiModule struct { - aiAPIService ai_api.IAPIService `autowired:""` - aiAPIUseService ai_api.IAPIUseService `autowired:""` + aiAPIService ai_api.IAPIService `autowired:""` + aiAPIUseService ai_api.IAPIUseService `autowired:""` + serviceService service.IServiceService `autowired:""` } -func (i *imlAIApiModule) APIs(ctx context.Context, keyword string, providerId string, start int64, end int64, page int, pageSize int, sortCondition string, asc bool) ([]*ai_dto.APIItem, int64, error) { +func (i *imlAIApiModule) APIs(ctx context.Context, keyword string, providerId string, start int64, end int64, page int, pageSize int, sortCondition string, asc bool, models []string, serviceIds []string) ([]*ai_dto.APIItem, *ai_dto.Condition, int64, error) { + p, has := model_runtime.GetProvider(providerId) + if !has { + return nil, nil, 0, fmt.Errorf("ai provider not found") + } sortRule := "desc" if asc { sortRule = "asc" } + services, err := i.serviceService.ServiceListByKind(ctx, service.AIService) + if err != nil { + return nil, nil, 0, err + } + serviceItems := make([]*ai_dto.BasicInfo, 0, len(services)) + serviceTeamMap := make(map[string]string) + for _, s := range services { + serviceItems = append(serviceItems, &ai_dto.BasicInfo{ + Id: s.Id, + Name: s.Name, + }) + serviceTeamMap[s.Id] = s.Team + + } + + modelItems := utils.SliceToSlice(p.Models(), func(e model_runtime.IModel) *ai_dto.BasicInfo { + return &ai_dto.BasicInfo{ + Id: e.ID(), + Name: e.ID(), + } + }) + condition := &ai_dto.Condition{Services: serviceItems, Models: modelItems} switch sortCondition { default: - apis, err := i.aiAPIService.Search(ctx, keyword, map[string]interface{}{ + w := map[string]interface{}{ "provider": providerId, - }, "update_at desc") + } + if len(models) > 0 { + w["model"] = models + } + if len(serviceIds) > 0 { + w["service"] = serviceIds + } + apis, err := i.aiAPIService.Search(ctx, keyword, w, "update_at desc") if err != nil { - return nil, 0, err + return nil, nil, 0, err } if len(apis) <= 0 { - return nil, 0, nil + return nil, condition, 0, nil } apiMap := make(map[string]*ai_api.API) apiIds := make([]string, 0, len(apis)) @@ -668,7 +763,7 @@ func (i *imlAIApiModule) APIs(ctx context.Context, keyword string, providerId st offset := (page - 1) * pageSize results, _, err := i.aiAPIUseService.SumByApisPage(ctx, providerId, start, end, offset, pageSize, fmt.Sprintf("total_token %s", sortRule), apiIds...) if err != nil { - return nil, 0, err + return nil, nil, 0, err } apiItems := utils.SliceToSlice(results, func(e *ai_api.APIUse) *ai_dto.APIItem { @@ -679,6 +774,7 @@ func (i *imlAIApiModule) APIs(ctx context.Context, keyword string, providerId st Id: e.API, Name: info.Name, Service: auto.UUID(info.Service), + Team: auto.UUID(serviceTeamMap[info.Service]), Method: http.MethodPost, RequestPath: info.Path, Model: auto.Label{ @@ -696,6 +792,7 @@ func (i *imlAIApiModule) APIs(ctx context.Context, keyword string, providerId st Id: a.ID, Name: a.Name, Service: auto.UUID(a.Service), + Team: auto.UUID(serviceTeamMap[a.Service]), Method: http.MethodPost, RequestPath: a.Path, Model: auto.Label{ @@ -715,7 +812,8 @@ func (i *imlAIApiModule) APIs(ctx context.Context, keyword string, providerId st for i := offset; i < offset+size && i < len(sortApis); i++ { apiItems = append(apiItems, sortApis[i]) } + total := int64(len(apis)) - return apiItems, total, nil + return apiItems, condition, total, nil } } diff --git a/module/ai/module.go b/module/ai/module.go index 176c5c38..99b6a56e 100644 --- a/module/ai/module.go +++ b/module/ai/module.go @@ -4,28 +4,27 @@ import ( "context" "reflect" - "github.com/eolinker/go-common/auto" - "github.com/APIParkLab/APIPark/gateway" ai_dto "github.com/APIParkLab/APIPark/module/ai/dto" "github.com/eolinker/go-common/autowire" ) type IProviderModule interface { - ConfiguredProviders(ctx context.Context) ([]*ai_dto.ConfiguredProviderItem, *auto.Label, error) + ConfiguredProviders(ctx context.Context) ([]*ai_dto.ConfiguredProviderItem, *ai_dto.BackupProvider, error) UnConfiguredProviders(ctx context.Context) ([]*ai_dto.ProviderItem, error) SimpleProviders(ctx context.Context) ([]*ai_dto.SimpleProviderItem, error) + SimpleConfiguredProviders(ctx context.Context) ([]*ai_dto.SimpleProviderItem, *ai_dto.BackupProvider, error) Provider(ctx context.Context, id string) (*ai_dto.Provider, error) SimpleProvider(ctx context.Context, id string) (*ai_dto.SimpleProvider, error) LLMs(ctx context.Context, driver string) ([]*ai_dto.LLMItem, *ai_dto.ProviderItem, error) - UpdateProviderStatus(ctx context.Context, id string, enable bool) error + //UpdateProviderStatus(ctx context.Context, id string, enable bool) error UpdateProviderConfig(ctx context.Context, id string, input *ai_dto.UpdateConfig) error - UpdateProviderDefaultLLM(ctx context.Context, id string, input *ai_dto.UpdateLLM) error + //UpdateProviderDefaultLLM(ctx context.Context, id string, input *ai_dto.UpdateLLM) error Sort(ctx context.Context, input *ai_dto.Sort) error } type IAIAPIModule interface { - APIs(ctx context.Context, keyword string, providerId string, start int64, end int64, page int, pageSize int, sortCondition string, asc bool) ([]*ai_dto.APIItem, int64, error) + APIs(ctx context.Context, keyword string, providerId string, start int64, end int64, page int, pageSize int, sortCondition string, asc bool, models []string, services []string) ([]*ai_dto.APIItem, *ai_dto.Condition, int64, error) } func init() { diff --git a/module/catalogue/iml.go b/module/catalogue/iml.go index 18b58def..c43de0c8 100644 --- a/module/catalogue/iml.go +++ b/module/catalogue/iml.go @@ -176,19 +176,6 @@ func (i *imlCatalogueModule) Subscribe(ctx context.Context, subscribeInfo *catal // 修改订阅表状态 subscribers, err := i.subscribeService.ListByApplication(ctx, subscribeInfo.Service, appId) if err != nil { - //if !errors.Is(err, gorm.ErrRecordNotFound) { - // return err - //} - //err = i.subscribeService.Create(ctx, &subscribe.CreateSubscribe{ - // Uuid: uuid.New().String(), - // Service: subscribeInfo.Service, - // Application: appId, - // ApplyStatus: status, - // From: subscribe.FromSubscribe, - //}) - //if err != nil { - // return err - //} return err } else { subscriberMap := utils.SliceToMap(subscribers, func(t *subscribe.Subscribe) string { diff --git a/module/cluster/dto/input.go b/module/cluster/dto/input.go index 9d690526..43036f37 100644 --- a/module/cluster/dto/input.go +++ b/module/cluster/dto/input.go @@ -17,12 +17,12 @@ package cluster_dto //type SaveMonitorConfig struct { // Driver string `json:"driver"` -// Config map[string]interface{} `json:"config"` +// DefaultConfig map[string]interface{} `json:"config"` //} //type MonitorConfig struct { // Driver string `json:"driver"` -// Config map[string]interface{} `json:"config"` +// DefaultConfig map[string]interface{} `json:"config"` //} //type MonitorPartition struct { diff --git a/module/publish/iml.go b/module/publish/iml.go index 0eb17754..4226ef1f 100644 --- a/module/publish/iml.go +++ b/module/publish/iml.go @@ -124,6 +124,7 @@ func (m *imlPublishModule) getProjectRelease(ctx context.Context, projectID stri Version: version, } apis := make([]*gateway.ApiRelease, 0, len(apiInfos)) + hasUpstream := len(upstreamCommitIds) > 0 for _, a := range apiInfos { apiInfo := &gateway.ApiRelease{ BasicItem: &gateway.BasicItem{ @@ -133,7 +134,10 @@ func (m *imlPublishModule) getProjectRelease(ctx context.Context, projectID stri }, Path: a.Path, Methods: a.Methods, - Service: a.Upstream, + //Service: a.Upstream, + } + if hasUpstream { + apiInfo.Service = a.Upstream } proxy, ok := proxyCommitMap[a.UUID] if ok { diff --git a/module/router/dto/input.go b/module/router/dto/input.go index 02fcbf10..593a3647 100644 --- a/module/router/dto/input.go +++ b/module/router/dto/input.go @@ -30,7 +30,7 @@ type Create struct { MatchRules []Match `json:"match"` Upstream string `json:"upstream"` Proxy *InputProxy `json:"proxy"` - Disable bool `json:"disable"` + Disable bool `json:"disabled"` } type InputProxy struct { @@ -69,7 +69,7 @@ type Edit struct { Methods *[]string `json:"methods"` Protocols *[]string `json:"protocols"` MatchRules *[]Match `json:"match"` - Disable *bool `json:"disable"` + Disable *bool `json:"disabled"` Upstream *string `json:"upstream"` } diff --git a/module/router/dto/output.go b/module/router/dto/output.go index 9f8276ed..50611785 100644 --- a/module/router/dto/output.go +++ b/module/router/dto/output.go @@ -15,7 +15,7 @@ type Item struct { Protocols []string `json:"protocols"` Path string `json:"request_path"` Description string `json:"description"` - Disable bool `json:"disable"` + Disable bool `json:"disabled"` Creator auto.Label `json:"creator" aolabel:"user"` Updater auto.Label `json:"updater" aolabel:"user"` CreateTime auto.TimeLabel `json:"create_time"` @@ -34,7 +34,7 @@ type Detail struct { SimpleDetail Proxy *Proxy `json:"proxy"` Protocols []string `json:"protocols"` - Disable bool `json:"disable"` + Disable bool `json:"disabled"` //Doc map[string]interface{} `json:"doc"` } diff --git a/module/service/iml.go b/module/service/iml.go index 377990bd..8edf26d3 100644 --- a/module/service/iml.go +++ b/module/service/iml.go @@ -92,11 +92,6 @@ func (i *imlServiceModule) ExportAll(ctx context.Context) ([]*service_dto.Export serviceTagMap[st.Sid] = append(serviceTagMap[st.Sid], tagMap[st.Tid].Name) } - //docMap, err := i.serviceDocService.Map(ctx, serviceIds...) - //if err != nil { - // return nil, err - //} - items := make([]*service_dto.ExportService, 0, len(services)) for _, s := range services { info := &service_dto.ExportService{ @@ -171,24 +166,6 @@ func (i *imlServiceModule) SearchMyServices(ctx context.Context, teamId string, return items, nil } -//func (i *imlServiceModule) SimpleAPPS(ctx context.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) { -// w := make(map[string]interface{}) -// w["as_app"] = true -// services, err := i.serviceService.SearchByDriver(ctx, keyword, w) -// if err != nil { -// return nil, err -// } -// return utils.SliceToSlice(services, func(p *service.Service) *service_dto.SimpleServiceItem { -// return &service_dto.SimpleServiceItem{ -// Id: p.Id, -// Name: p.Name, -// Description: p.Description, -// -// Team: auto.UUID(p.Team), -// } -// }), nil -//} - func (i *imlServiceModule) Simple(ctx context.Context) ([]*service_dto.SimpleServiceItem, error) { w := make(map[string]interface{}) w["as_server"] = true diff --git a/module/subscribe/iml.go b/module/subscribe/iml.go index 70e43bd2..03dc7935 100644 --- a/module/subscribe/iml.go +++ b/module/subscribe/iml.go @@ -218,33 +218,46 @@ func (i *imlSubscribeModule) AddSubscriber(ctx context.Context, serviceId string if err != nil { return err } - _, err = i.subscribeService.GetByServiceAndApplication(ctx, serviceId, input.Application) - if err == nil { - // 订阅方已存在 - return fmt.Errorf("subscriber is already exists") + clusters, err := i.clusterService.List(ctx) + if err != nil { + return err } - sub := &gateway.SubscribeRelease{ Service: serviceId, Application: input.Application, Expired: "0", } - clusters, err := i.clusterService.List(ctx) - if err != nil { - return err - } - return i.transaction.Transaction(ctx, func(ctx context.Context) error { - err = i.subscribeService.Create(ctx, &subscribe.CreateSubscribe{ - Uuid: uuid.New().String(), - Service: serviceId, - Application: input.Application, - ApplyStatus: subscribe.ApplyStatusSubscribe, - From: subscribe.FromUser, - }) - if err != nil { - return err + info, err := i.subscribeService.GetByServiceAndApplication(ctx, serviceId, input.Application) + if err == nil { + // 订阅方已存在 + if info.ApplyStatus != subscribe.ApplyStatusSubscribe { + // 更新订阅方状态 + status := subscribe.ApplyStatusSubscribe + from := subscribe.FromUser + err = i.subscribeService.Save(ctx, info.Id, &subscribe.UpdateSubscribe{ + ApplyStatus: &status, + From: &from, + }) + if err != nil { + return err + } + } else { + return nil + } + } else { + err = i.subscribeService.Create(ctx, &subscribe.CreateSubscribe{ + Uuid: uuid.New().String(), + Service: serviceId, + Application: input.Application, + ApplyStatus: subscribe.ApplyStatusSubscribe, + From: subscribe.FromUser, + }) + if err != nil { + return err + } } + for _, c := range clusters { err = i.onlineSubscriber(ctx, c.Uuid, sub) if err != nil { diff --git a/plugins/core/ai.go b/plugins/core/ai.go index cc45281b..e42a09db 100644 --- a/plugins/core/ai.go +++ b/plugins/core/ai.go @@ -13,20 +13,21 @@ func (p *plugin) aiAPIs() []pm3.Api { pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/providers/unconfigured", []string{"context"}, []string{"providers"}, p.aiProviderController.UnConfiguredProviders, access.SystemSettingsAiProviderView), pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/providers/configured", []string{"context"}, []string{"providers", "backup"}, p.aiProviderController.ConfiguredProviders, access.SystemSettingsAiProviderView), pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/simple/ai/providers", []string{"context"}, []string{"providers"}, p.aiProviderController.SimpleProviders), + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/simple/ai/providers/configured", []string{"context"}, []string{"providers", "backup"}, p.aiProviderController.SimpleConfiguredProviders), pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/provider/config", []string{"context", "query:provider"}, []string{"provider"}, p.aiProviderController.Provider, access.SystemSettingsAiProviderView), pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/simple/ai/provider", []string{"context", "query:provider"}, []string{"provider"}, p.aiProviderController.SimpleProvider), pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/provider/llms", []string{"context", "query:provider"}, []string{"llms", "provider"}, p.aiProviderController.LLMs), pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai/provider/sort", []string{"context", "body"}, nil, p.aiProviderController.Sort), pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai/provider/config", []string{"context", "query:provider", "body"}, nil, p.aiProviderController.UpdateProviderConfig, access.SystemSettingsAiProviderManager), - pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/apis", []string{"context", "query:keyword", "query:provider", "query:start", "query:end", "query:page", "query:page_size", "query:sort", "query:asc"}, []string{"apis", "total"}, p.aiStatisticController.APIs), + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/apis", []string{"context", "query:keyword", "query:provider", "query:start", "query:end", "query:page", "query:page_size", "query:sort", "query:asc", "query:models", "query:services"}, []string{"apis", "condition", "total"}, p.aiStatisticController.APIs), } } func (p *plugin) aiKeyApis() []pm3.Api { return []pm3.Api{ pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/resource/key", []string{"context", "query:provider", "query:id"}, []string{"info"}, p.aiKeyController.Get), - pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/resource/keys", []string{"context", "query:provider", "query:keyword", "query:page", "query:page_size"}, []string{"keys", "total"}, p.aiKeyController.List), + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/resource/keys", []string{"context", "query:provider", "query:keyword", "query:page", "query:page_size", "query:statuses"}, []string{"keys", "total"}, p.aiKeyController.List), pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/ai/resource/key", []string{"context", "query:provider", "body"}, nil, p.aiKeyController.Create), pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai/resource/key", []string{"context", "query:provider", "query:id", "body"}, nil, p.aiKeyController.Edit), pm3.CreateApiWidthDoc(http.MethodDelete, "/api/v1/ai/resource/key", []string{"context", "query:provider", "query:id"}, nil, p.aiKeyController.Delete), diff --git a/scripts/Dockerfile b/scripts/Dockerfile index f78900c8..71b5c4e3 100755 --- a/scripts/Dockerfile +++ b/scripts/Dockerfile @@ -8,6 +8,9 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone ARG APP +ENV NSQ_ADDR=nsq:4150 +ENV NSQ_TOPIC_PREFIX=apipark + RUN mkdir -p /${APP} COPY cmd/* /${APP}/ diff --git a/scripts/build.sh b/scripts/build.sh index 47747583..0b6c85dc 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -8,6 +8,8 @@ source ./scripts/common.sh OUTPUT_DIR=$(mkdir_output "$1") APP="apipark" OUTPUT_BIN="${OUTPUT_DIR}/${APP}" +AI_EVENT_LISTEN_APP="apipark_ai_event_listen" +AI_EVENT_LISTEN_BIN="${OUTPUT_DIR}/${AI_EVENT_LISTEN_APP}" VERSION=$(gen_version "$2") BUILD_TYPE=$3 ARCH=$4 @@ -104,6 +106,10 @@ build_backend() { # -ldflags="-w -s" means omit DWARF symbol table and the symbol table and debug information echo "GOOS=linux GOARCH=$ARCH CGO_ENABLED=0 go build $Tags -ldflags \"-w -s $flags\" -o \"${OUTPUT_BIN}\"" GOOS=linux GOARCH=$ARCH CGO_ENABLED=0 go build ${Tags} -ldflags "-w -s $flags" -o ${OUTPUT_BIN} + + echo "Build backend successfully..." + echo "GOOS=linux GOARCH=$ARCH CGO_ENABLED=0 go build -ldflags \"-w -s\" -o \"${AI_EVENT_LISTEN_BIN}\" ./app/ai-event-handler" + GOOS=linux GOARCH=$ARCH CGO_ENABLED=0 go build -ldflags "-w -s" -o "${AI_EVENT_LISTEN_BIN}" ./app/ai-event-handler return } @@ -123,6 +129,9 @@ package() { cp "${OUTPUT_BIN}" "${PACKAGE_DIR}" + echo "cp ${AI_EVENT_LISTEN_BIN} ${PACKAGE_DIR}" + cp "${AI_EVENT_LISTEN_BIN}" "${PACKAGE_DIR}" + echo "tar -czvf ${PACKAGE_DIR}_linux_${ARCH}.tar.gz -C ${PACKAGE_DIR}/ ./" tar -czvf "${PACKAGE_DIR}_linux_${ARCH}.tar.gz" -C "${PACKAGE_DIR}/" "./" # rm -fr "${PACKAGE_DIR}" diff --git a/scripts/resource/docker_run.sh b/scripts/resource/docker_run.sh index 53d3d711..caaac971 100755 --- a/scripts/resource/docker_run.sh +++ b/scripts/resource/docker_run.sh @@ -21,6 +21,9 @@ echo -e "redis:" >> config.yml echo -e " user_name: ${REDIS_USER_NAME}" >> config.yml echo -e " password: ${REDIS_PWD}" >> config.yml echo -e " addr: " >> config.yml +echo -e "nsq:" >> config.yml +echo -e " addr: ${NSQ_ADDR}" >> config.yml +echo -e " topic: ${NSQ_TOPIC}" >> config.yml for s in ${arr[@]} do echo -e " - $s" >> config.yml @@ -34,4 +37,6 @@ echo -e " log_expire: ${ERROR_EXPIRE}" >> config.yml echo -e " log_period: ${ERROR_PERIOD}" >> config.yml cat config.yml -./apipark \ No newline at end of file +nohup ./apipark >> run.log 2>&1 & +nohup ./apipark_ai_event_listen >> run.log 2>&1 & +tail -F run.log \ No newline at end of file diff --git a/scripts/resource/run.sh b/scripts/resource/run.sh index cc998bd9..6aff686b 100755 --- a/scripts/resource/run.sh +++ b/scripts/resource/run.sh @@ -42,7 +42,8 @@ start() { cat "$LOG_FILE" exit 1 fi - + # 启动ai事件监听程序 +# nohup ./apipark_ai_event_listen >> "$LOG_FILE" 2>&1 & } # 停止函数 diff --git a/service/ai-api/iml.go b/service/ai-api/iml.go index 97e157c2..b59a004e 100644 --- a/service/ai-api/iml.go +++ b/service/ai-api/iml.go @@ -3,9 +3,10 @@ package ai_api import ( "context" "encoding/json" + "errors" "time" - "github.com/eolinker/go-common/utils" + "gorm.io/gorm" "github.com/APIParkLab/APIPark/service/universally" "github.com/APIParkLab/APIPark/stores/api" @@ -34,7 +35,7 @@ func (i *imlAPIService) OnComplete() { } func labelHandler(e *api.AiAPIInfo) []string { - return []string{e.Name, e.Uuid} + return []string{e.Name, e.Path} } func uniquestHandler(i *Create) []map[string]interface{} { return []map[string]interface{}{{"uuid": i.ID}} @@ -87,7 +88,9 @@ func updateHandler(e *api.AiAPIInfo, i *Edit) { if i.Disable != nil { e.Disable = *i.Disable } - + if i.UseToken != nil { + e.UseToken = *i.UseToken + } e.UpdateAt = time.Now() } @@ -97,6 +100,36 @@ type imlAPIUseService struct { store api.IAiAPIUseStore `autowired:""` } +func (i *imlAPIUseService) Incr(ctx context.Context, incr *IncrAPIUse) error { + info, err := i.store.First(ctx, map[string]interface{}{ + "api": incr.API, + "service": incr.Service, + "provider": incr.Provider, + "model": incr.Model, + "day": incr.Day, + "hour": incr.Hour, + "minute": incr.Minute, + }) + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return err + } + info = &api.AiAPIUse{ + API: incr.API, + Service: incr.Service, + Provider: incr.Provider, + Model: incr.Model, + Day: incr.Day, + Hour: incr.Hour, + Minute: incr.Minute, + } + } + info.InputToken += incr.InputToken + info.OutputToken += incr.OutputToken + info.TotalToken += incr.TotalToken + return i.store.Save(ctx, info) +} + func (i *imlAPIUseService) SumByApisPage(ctx context.Context, providerId string, start, end int64, offset, limit int, order string, apiIds ...string) ([]*APIUse, int64, error) { list, total, err := i.store.SumByGroupPage(ctx, "api", order, offset, limit, "api,sum(input_token) as input_token,sum(output_token) as output_token,sum(total_token) as total_token", "provider = ? and api in (?) and minute >= ? and minute <= ?", providerId, apiIds, start, end) if err != nil { @@ -116,17 +149,18 @@ func (i *imlAPIUseService) SumByApisPage(ctx context.Context, providerId string, } func (i *imlAPIUseService) SumByApis(ctx context.Context, providerId string, start, end int64, apiIds ...string) ([]*APIUse, error) { - list, err := i.store.SumByGroup(ctx, "api", "api,sum(input_token) as input_token,sum(output_token) as output_token,sum(total_token) as total_token", "provider = ? and api in (?) and minute >= ? and minute <= ?", providerId, apiIds, start, end) - if err != nil { - return nil, err - } - - return utils.SliceToSlice(list, func(v *api.AiAPIUse) *APIUse { - return &APIUse{ - API: v.API, - InputToken: v.InputToken, - OutputToken: v.OutputToken, - TotalToken: v.TotalToken, - } - }), nil + //list, err := i.store.SumByGroup(ctx, "api", "api,sum(input_token) as input_token,sum(output_token) as output_token,sum(total_token) as total_token", "provider = ? and api in (?) and minute >= ? and minute <= ?", providerId, apiIds, start, end) + //if err != nil { + // return nil, err + //} + // + //return utils.SliceToSlice(list, func(v *api.AiAPIUse) *APIUse { + // return &APIUse{ + // API: v.API, + // InputToken: v.InputToken, + // OutputToken: v.OutputToken, + // TotalToken: v.TotalToken, + // } + //}), nil + return nil, nil } diff --git a/service/ai-api/model.go b/service/ai-api/model.go index 9ba1e635..b8ee9cb7 100644 --- a/service/ai-api/model.go +++ b/service/ai-api/model.go @@ -19,6 +19,7 @@ type API struct { Provider string CreateAt time.Time UpdateAt time.Time + UseToken int Creator string Updater string AdditionalConfig map[string]interface{} @@ -48,6 +49,7 @@ type Edit struct { Provider *string Model *string Disable *bool + UseToken *int AdditionalConfig *map[string]interface{} } @@ -70,6 +72,7 @@ func FromEntity(e *api.AiAPIInfo) *API { Creator: e.Creator, Updater: e.Updater, Disable: e.Disable, + UseToken: e.UseToken, AdditionalConfig: cfg, } } @@ -80,3 +83,16 @@ type APIUse struct { OutputToken int TotalToken int } + +type IncrAPIUse struct { + API string + Service string + Provider string + Model string + Day int64 + Hour int64 + Minute int64 + InputToken int + OutputToken int + TotalToken int +} diff --git a/service/ai-api/service.go b/service/ai-api/service.go index fc411a3a..de841fda 100644 --- a/service/ai-api/service.go +++ b/service/ai-api/service.go @@ -19,6 +19,7 @@ type IAPIService interface { type IAPIUseService interface { SumByApis(ctx context.Context, providerId string, start, end int64, apiIds ...string) ([]*APIUse, error) SumByApisPage(ctx context.Context, providerId string, start, end int64, page, pageSize int, order string, apiIds ...string) ([]*APIUse, int64, error) + Incr(ctx context.Context, incr *IncrAPIUse) error } func init() { diff --git a/service/ai-key/iml.go b/service/ai-key/iml.go index 414b5028..302ec182 100644 --- a/service/ai-key/iml.go +++ b/service/ai-key/iml.go @@ -23,6 +23,39 @@ type imlAIKeyService struct { universally.IServiceDelete } +func (i *imlAIKeyService) IncrUseToken(ctx context.Context, id string, useToken int) error { + info, err := i.store.GetByUUID(ctx, id) + if err != nil { + return err + } + + info.UseToken += useToken + return i.store.Save(ctx, info) +} + +func (i *imlAIKeyService) SearchUnExpiredByPage(ctx context.Context, w map[string]interface{}, page, pageSize int, order string) ([]*Key, int64, error) { + sql := "(expire_time = 0 || expire_time > ?)" + args := []interface{}{time.Now().Unix()} + for k, v := range w { + switch v.(type) { + case []int: + sql += fmt.Sprintf(" and `%s` in (?)", k) + default: + sql += fmt.Sprintf(" and `%s` = ?", k) + } + args = append(args, v) + } + list, total, err := i.store.ListPage(ctx, sql, page, pageSize, args, order) + if err != nil { + return nil, 0, err + } + var result []*Key + for _, item := range list { + result = append(result, FromEntity(item)) + } + return result, total, nil +} + func (i *imlAIKeyService) KeysAfterPriority(ctx context.Context, providerId string, priority int) ([]*Key, error) { list, err := i.store.ListQuery(ctx, "sort > ? and provider = ?", []interface{}{priority, providerId}, "sort asc") if err != nil { @@ -169,7 +202,7 @@ func (i *imlAIKeyService) OnComplete() { } func labelHandler(e *ai.Key) []string { - return []string{e.Name, e.Uuid} + return []string{e.Name} } func uniquestHandler(i *Create) []map[string]interface{} { return []map[string]interface{}{{"uuid": i.ID}} @@ -206,5 +239,6 @@ func updateHandler(e *ai.Key, i *Edit) { if i.Priority != nil { e.Sort = *i.Priority } + e.UpdateAt = time.Now() } diff --git a/service/ai-key/service.go b/service/ai-key/service.go index 6cbd8739..8bbe6d83 100644 --- a/service/ai-key/service.go +++ b/service/ai-key/service.go @@ -20,6 +20,8 @@ type IKeyService interface { SortBefore(ctx context.Context, provider string, originID string, targetID string) ([]*Key, error) SortAfter(ctx context.Context, provider string, originID string, targetID string) ([]*Key, error) KeysAfterPriority(ctx context.Context, providerId string, priority int) ([]*Key, error) + SearchUnExpiredByPage(ctx context.Context, w map[string]interface{}, page, pageSize int, order string) ([]*Key, int64, error) + IncrUseToken(ctx context.Context, id string, useToken int) error } func init() { diff --git a/service/service/iml.go b/service/service/iml.go index c3090ea0..719e55e7 100644 --- a/service/service/iml.go +++ b/service/service/iml.go @@ -46,7 +46,7 @@ func (i *imlServiceService) ServiceListByKind(ctx context.Context, kind Kind, se w["uuid"] = serviceIds } w["as_server"] = true - w["kind"] = kind + w["kind"] = kind.Int() w["is_delete"] = false list, err := i.store.List(ctx, w) if err != nil { diff --git a/service/subscribe/iml.go b/service/subscribe/iml.go index dd3fd460..92d0b4f6 100644 --- a/service/subscribe/iml.go +++ b/service/subscribe/iml.go @@ -200,6 +200,9 @@ func (i *imlSubscribeService) updateHandler(e *subscribe.Subscribe, t *UpdateSub if t.ApplyStatus != nil { e.ApplyStatus = *t.ApplyStatus } + if t.From != nil { + e.From = *t.From + } } var ( diff --git a/service/subscribe/model.go b/service/subscribe/model.go index d9882145..365a9d4d 100644 --- a/service/subscribe/model.go +++ b/service/subscribe/model.go @@ -2,14 +2,14 @@ package subscribe import ( "time" - + "github.com/APIParkLab/APIPark/stores/subscribe" ) type Subscribe struct { Id string Service string - + // 订阅方相关 Application string From int @@ -28,6 +28,7 @@ type CreateSubscribe struct { } type UpdateSubscribe struct { + From *int ApplyStatus *int } diff --git a/stores/api/model.go b/stores/api/model.go index b4b1add3..1cfb6106 100644 --- a/stores/api/model.go +++ b/stores/api/model.go @@ -103,9 +103,10 @@ type AiAPIUse struct { API string `gorm:"size:36;not null;column:api;comment:API;index:api"` Service string `gorm:"size:36;not null;column:service;comment:服务;index:service"` Provider string `gorm:"size:36;not null;column:provider;comment:提供者;index:provider"` - Day int `gorm:"type:int(11);not null;column:day;comment:当前日期"` - Hour int `gorm:"type:int(11);not null;column:hour;comment:当前小时"` - Minute int `gorm:"type:int(11);not null;column:minute;comment:当前分钟"` + Model string `gorm:"size:255;not null;column:model;comment:模型"` + Day int64 `gorm:"type:int(11);not null;column:day;comment:当前日期"` + Hour int64 `gorm:"type:int(11);not null;column:hour;comment:当前小时"` + Minute int64 `gorm:"type:int(11);not null;column:minute;comment:当前分钟"` InputToken int `gorm:"type:int(11);not null;column:input_token;comment:输入token"` OutputToken int `gorm:"type:int(11);not null;column:output_token;comment:输出token"` TotalToken int `gorm:"type:int(11);not null;column:total_token;comment:总token"`