#4193 V20230517

Merged
ychao_1983 merged 176 commits from V20230517 into develop 11 months ago
  1. +2
    -2
      models/ai_model_manage.go
  2. +35
    -3
      models/cloudbrain.go
  3. +8
    -0
      models/error.go
  4. +243
    -0
      models/modelarts_deploy.go
  5. +61
    -0
      models/modelarts_deploy_queue.go
  6. +2
    -0
      models/models.go
  7. +11
    -7
      models/resource_scene.go
  8. +28
    -9
      models/resource_specification.go
  9. +7
    -0
      modules/auth/cloudbrain.go
  10. +45
    -0
      modules/auth/wechat/finetune.go
  11. +4
    -2
      modules/auth/wechat/template.go
  12. +3
    -1
      modules/cloudbrain/cloudbrain.go
  13. +17
    -0
      modules/convert/cloudbrain.go
  14. +24
    -0
      modules/convert/finetune.go
  15. +29
    -2
      modules/cron/tasks_basic.go
  16. +24
    -21
      modules/grampus/grampus.go
  17. +144
    -0
      modules/modelarts/modelarts.go
  18. +364
    -0
      modules/modelarts/resty.go
  19. +1
    -0
      modules/notification/base/notifier.go
  20. +4
    -0
      modules/notification/base/null.go
  21. +7
    -0
      modules/notification/notification.go
  22. +6
    -0
      modules/notification/wechat/wechat.go
  23. +105
    -0
      modules/setting/finetune.go
  24. +1
    -0
      modules/setting/setting.go
  25. +60
    -18
      modules/structs/cloudbrain.go
  26. +14
    -0
      modules/structs/finetune.go
  27. +35
    -11
      options/locale/locale_en-US.ini
  28. +27
    -4
      options/locale/locale_zh-CN.ini
  29. BIN
      public/img/ros-hmci/mbz612.png
  30. +11
    -9
      routers/admin/resources.go
  31. +66
    -45
      routers/api/v1/api.go
  32. +249
    -0
      routers/api/v1/finetune/finetune.go
  33. +277
    -0
      routers/api/v1/finetune/panguervice.go
  34. +8
    -2
      routers/api/v1/repo/modelarts.go
  35. +22
    -0
      routers/api/v1/user/cloudbrain.go
  36. +31
    -0
      routers/modelapp/pangu.go
  37. +11
    -8
      routers/repo/ai_model_manage.go
  38. +22
    -6
      routers/repo/ai_model_square.go
  39. +28
    -16
      routers/repo/attachment.go
  40. +12
    -8
      routers/repo/cloudbrain.go
  41. +287
    -0
      routers/repo/dataset.go
  42. +47
    -12
      routers/repo/grampus.go
  43. +62
    -41
      routers/repo/modelarts.go
  44. +14
    -0
      routers/repo/setting.go
  45. +2
    -0
      routers/repo/util.go
  46. +18
    -1
      routers/routes/routes.go
  47. +3
    -0
      routers/search.go
  48. +2
    -2
      services/ai_task_service/task/cloudbrain_one_notebook_task.go
  49. +1
    -1
      services/ai_task_service/task/grampus_notebook_task.go
  50. +2
    -2
      services/ai_task_service/task/grampus_train_task.go
  51. +15
    -7
      services/cloudbrain/cloudbrainTask/train.go
  52. +15
    -0
      services/cloudbrain/resource/resource_specification.go
  53. +1
    -1
      templates/admin/cloudbrain/list.tmpl
  54. +1
    -1
      templates/base/footer_content.tmpl
  55. +2
    -1
      templates/base/footer_content_fluid.tmpl
  56. +1
    -1
      templates/base/head.tmpl
  57. +1
    -1
      templates/base/head_course.tmpl
  58. +1
    -1
      templates/base/head_fluid.tmpl
  59. +1
    -1
      templates/base/head_home.tmpl
  60. +4
    -2
      templates/base/head_navbar.tmpl
  61. +4
    -2
      templates/base/head_navbar_fluid.tmpl
  62. +4
    -2
      templates/base/head_navbar_home.tmpl
  63. +4
    -2
      templates/base/head_navbar_pro.tmpl
  64. +72
    -0
      templates/custom/export_dataset.tmpl
  65. +1
    -1
      templates/custom/home/home_top.tmpl
  66. +5
    -0
      templates/model/base/index.tmpl
  67. +6
    -0
      templates/model/base/panguCreate.tmpl
  68. +5
    -0
      templates/model/base/panguIndex.tmpl
  69. +5
    -0
      templates/model/base/panguInference.tmpl
  70. +2
    -2
      templates/repo/cloudbrain/benchmark/new.tmpl
  71. +1
    -1
      templates/repo/cloudbrain/benchmark/show.tmpl
  72. +9
    -3
      templates/repo/cloudbrain/image/edit.tmpl
  73. +8
    -2
      templates/repo/cloudbrain/image/submit.tmpl
  74. +1
    -1
      templates/repo/cloudbrain/inference/new.tmpl
  75. +1
    -1
      templates/repo/cloudbrain/new.tmpl
  76. +1
    -1
      templates/repo/cloudbrain/show.tmpl
  77. +1
    -1
      templates/repo/cloudbrain/trainjob/new.tmpl
  78. +13
    -3
      templates/repo/cloudbrain/trainjob/show.tmpl
  79. +1
    -1
      templates/repo/grampus/notebook/gcu/new.tmpl
  80. +1
    -1
      templates/repo/grampus/notebook/gpu/new.tmpl
  81. +1
    -1
      templates/repo/grampus/notebook/npu/new.tmpl
  82. +17
    -1
      templates/repo/grampus/notebook/show.tmpl
  83. +1
    -1
      templates/repo/grampus/trainjob/gcu/new.tmpl
  84. +1
    -1
      templates/repo/grampus/trainjob/gpu/new.tmpl
  85. +4
    -4
      templates/repo/grampus/trainjob/npu/new.tmpl
  86. +13
    -3
      templates/repo/grampus/trainjob/show.tmpl
  87. +2
    -2
      templates/repo/modelarts/inferencejob/new.tmpl
  88. +1
    -1
      templates/repo/modelarts/notebook/new.tmpl
  89. +1
    -1
      templates/repo/modelarts/notebook/show.tmpl
  90. +2
    -2
      templates/repo/modelarts/trainjob/index.tmpl
  91. +2
    -2
      templates/repo/modelarts/trainjob/new.tmpl
  92. +16
    -7
      templates/repo/modelarts/trainjob/show.tmpl
  93. +2
    -2
      templates/repo/modelarts/trainjob/version_new.tmpl
  94. +1
    -1
      templates/repo/modelsafety/new.tmpl
  95. +1
    -1
      templates/user/dashboard/cloudbrains.tmpl
  96. +30
    -19
      web_src/js/components/model/ModelSelect.vue
  97. +325
    -12
      web_src/js/features/cloudbrainShow.js
  98. +8
    -0
      web_src/js/features/cloudrbanin.js
  99. +15
    -1
      web_src/js/features/i18nVue.js
  100. +2
    -2
      web_src/js/features/images.js

+ 2
- 2
models/ai_model_manage.go View File

@@ -49,6 +49,7 @@ type AiModelManage struct {
RepoName string `xorm:"-" json:"repoName"`
RepoDisplayName string `xorm:"-" json:"repoDisplayName"`
RepoOwnerName string `xorm:"-" json:"repoOwnerName"`
DatasetInfo []*DatasetDownload `xorm:"-" json:"datasetInfo"`
ReferenceCount int `xorm:"NOT NULL DEFAULT 0" json:"referenceCount"`
CollectedCount int `xorm:"NOT NULL DEFAULT 0" json:"collectedCount"`
ModelFileList []storage.FileInfo `xorm:"-" json:"modelFileList"`
@@ -762,13 +763,12 @@ func DeleteModelFile(modelFile *AiModelFile) error {
func QueryModelFileByModelId(modelId string) []*AiModelFile {
sess := x.NewSession()
defer sess.Close()
modelFileList := make([]*AiModelFile, 0)
var cond = builder.NewCond()
cond = cond.And(
builder.Eq{"model_id": modelId},
)
result := make([]*AiModelFile, 0)
err := sess.Table(new(AiModelFile)).Where(cond).Find(&modelFileList)
err := sess.Table(new(AiModelFile)).Where(cond).Find(&result)
if err != nil {
log.Info("query AiModelFile failed, err=" + err.Error())
}


+ 35
- 3
models/cloudbrain.go View File

@@ -34,6 +34,16 @@ const (
JobNoTeminal = -1
)

const (
PanguCustom int = iota
PanguTextClassification
PanguTranslation
PanguOpenDialog
)
const (
PanguModelFineTune int = iota
)

const (
NPUResource = "NPU"
GPUResource = "CPU/GPU"
@@ -258,7 +268,10 @@ type Cloudbrain struct {
BenchmarkTypeRankLink string `xorm:"-"`
StartTime timeutil.TimeStamp
EndTime timeutil.TimeStamp
Cleared bool `xorm:"DEFAULT false"`
Cleared bool `xorm:"DEFAULT false"`
FineTune bool `xorm:"DEFAULT false"`
FineTuneModelType int
FineTuneCategory int
Spec *Specification `xorm:"-"`
}

@@ -1448,6 +1461,11 @@ type Datasurl struct {
DatasetName string `json:"dataset_name"`
}

type ModelUrls struct {
ModelUrl string `json:"model_url"`
ModelName string `json:"model_name"`
}

type DatasetDownload struct {
DatasetName string `json:"dataset_name"`
DatasetDownloadLink string `json:"dataset_download_link"`
@@ -2240,8 +2258,9 @@ func updateReferenceCount(cloudbrain *Cloudbrain) {
}

func increaseImageUseCount(image string) {

x.Exec("UPDATE `image` SET use_count=use_count+1 WHERE place=?", image)
if image != "" {
x.Exec("UPDATE `image` SET use_count=use_count+1 WHERE place=?", image)
}
}

func increaseModelReference(modelId string) {
@@ -3118,6 +3137,19 @@ func GetNewestJobsByType() ([]int64, error) {
Table(Cloudbrain{}).
Find(&ids)
}
func GetFinetuneCloudbrainsByUser(uid int64) ([]*Cloudbrain, error) {
cloudbrains := make([]*Cloudbrain, 0)
return cloudbrains, x.
Where("fine_tune=true and user_id=?", uid).Desc("id").
Find(&cloudbrains)
}

func GetFinetuneCloudbrainsCountByUser(uid int64) (int64, error) {
cloudbrain := new(Cloudbrain)
return x.
Where("fine_tune=true and user_id=?", uid).
Count(cloudbrain)
}

func GetCloudbrainByIDs(ids []int64) ([]*Cloudbrain, error) {
cloudbrains := make([]*Cloudbrain, 0)


+ 8
- 0
models/error.go View File

@@ -2062,3 +2062,11 @@ func IsNetworkError(err error) bool {
func (err NetworkError) Error() string {
return fmt.Sprintf("Network error")
}

type ErrModelartsDeployNotExist struct {
ID string
}

func (err ErrModelartsDeployNotExist) Error() string {
return fmt.Sprintf("Deployment %s does not exist", err.ID)
}

+ 243
- 0
models/modelarts_deploy.go View File

@@ -0,0 +1,243 @@
package models

import (
"time"

"code.gitea.io/gitea/modules/timeutil"
)

type ModelartsDeploy struct {
JobID string `xorm:"pk 'job_id'"`
JobName string
DisplayJobName string
UserID int64
Status string
ModelID string
ModelType string
ModelName string
ModelStatus string
ServiceID string
ServiceName string
ServiceStatus string
CreateUnix timeutil.TimeStamp
UpdateUnix timeutil.TimeStamp
CompleteUnix timeutil.TimeStamp
InferAddr string
DeployUrl string
Finetune bool
FinetuneModelType int
FinetuneCategory int
DeletedAt time.Time `xorm:"deleted"`
}

type CreateDeployModelParams struct {
ModelName string `json:"model_name"`
ModelVersion string `json:"model_version"`
ModelType string `json:"model_type"`
SourceLocation string `json:"source_location"`
Runtime string `json:"runtime"`
InstallType []string `json:"install_type"`
Prebuild bool `json:"prebuild"`
}

type CreateDeployModelResult struct {
ModelID string `json:"model_id"`
}

type GetDeployModelResult struct {
SourceLocation string `json:"source_location"`
ModelName string `json:"model_name"`
ModelID string `json:"model_id"`
ModelStatus string `json:"model_status"`
}

type DelDeployModelFail struct {
ModelID string `json:"model_id"`
ErrorCode string `json:"error_code"`
ErrorMsg string `json:"error_message"`
}

type CreateDeployServiceParams struct {
InferType string `json:"infer_type"`
ServiceName string `json:"service_name"`
ClusterID string `json:"cluster_id"`
Config []ServiceConfig `json:"config"`
Schedule []DeploySchedule `json:"schedule"`
}

type ServiceConfig struct {
Specification string `json:"specification"`
ModelID string `json:"model_id"`
InstanceCount int `json:"instance_count"`
//CustomSpec ServiceCustomSpec `json:"custom_spec"`
}

type ServiceCustomSpec struct {
CPU int `json:"cpu"`
Memory int `json:"memory"`
Ascend int `json:"ascend_a310"`
CPUInfo CPUInfo `json:"cpu_info"`
MemoryInfo MemoryInfo `json:"memory_info"`
NPUInfo NPUInfo `json:"npu_info"`
}

type CPUInfo struct {
CPU int `json:"cpu"`
Arch string `json:"arch"`
}

type MemoryInfo struct {
Memory int `json:"memory"`
Unit string `json:"unit"`
}

type NPUInfo struct {
NPU int `json:"npu"`
Brand string `json:"brand"`
Version string `json:"version"`
Unit string `json:"unit"`
Memory int `json:"memory"`
}

type DeploySchedule struct {
Duration int `json:"duration"`
TimeUnit string `json:"time_unit"`
Type string `json:"type"`
}

type CreateDeployServiceResult struct {
ServiceID string `json:"service_id"`
ResourceIDs []string `json:"resource_ids"`
}

type GetDeployServiceResult struct {
ServiceID string `json:"service_id"`
ServiceName string `json:"service_name"`
Status string `json:"status"`
InferAddr string `json:"access_address"`
}

type UpdateDeployServiceParams struct {
Status string `json:"status"`
}

type PanguInferParams struct {
Text string `json:"text"`
}
type PanguInferResult struct {
GenerateResult string `json:"generate_result"`
}

type PanguInferError struct {
ErrorCode string `json:"erno"`
ErrorMsg string `json:"msg"`
}

func CreateModelartsDeploy(deploy *ModelartsDeploy) (err error) {

sess := x.NewSession()
defer sess.Close()

if err := sess.Begin(); err != nil {
return err
}

if _, err = sess.Insert(deploy); err != nil {
return err
}
return sess.Commit()
}

// write a function to get the deployment by job id
func GetModelartsDeployByJobID(jobID string) (deploy *ModelartsDeploy, err error) {
deploy = new(ModelartsDeploy)
has, err := x.Where("job_id = ?", jobID).Get(deploy)
if err != nil {
return nil, err
} else if !has {
return nil, ErrModelartsDeployNotExist{jobID}
}
return deploy, nil
}

func UpdateDeploy(deploy *ModelartsDeploy) error {
return updateDeploy(x, deploy)
}

func updateDeploy(e Engine, deploy *ModelartsDeploy) error {
_, err := e.ID(deploy.JobID).AllCols().Update(deploy)
return err
}

// write a function return all deployments
func GetAllModelartsDeploys() ([]*ModelartsDeploy, error) {
return getAllModelartsDeploys(x)
}

func getAllModelartsDeploys(e Engine) ([]*ModelartsDeploy, error) {
deploys := make([]*ModelartsDeploy, 0)
return deploys, e.Find(&deploys)
}

// get deployment status by jobid
func GetModelartsDeployStatusByJobID(jobID string) (status string, err error) {
deploy, err := GetModelartsDeployByJobID(jobID)
if err != nil {
return "", err
}
return deploy.Status, nil
}

func GetRunningServiceByUser(userID int64) ([]*ModelartsDeploy, error) {
return getRunningServiceByUser(x, userID)
}
func getRunningServiceByUser(e Engine, userID int64) ([]*ModelartsDeploy, error) {
deploys := make([]*ModelartsDeploy, 0, 10)
return deploys, e.Where("user_id = ? AND status IN ('BUILDING','SUCCEEDED', 'DEPLOYING', 'WAITING')", userID).Find(&deploys)
}

func GetAllRunningService() ([]*ModelartsDeploy, error) {
return getAllRunningService(x)
}
func getAllRunningService(e Engine) ([]*ModelartsDeploy, error) {
deploys := make([]*ModelartsDeploy, 0, 10)
return deploys, e.Where("service_status IN ('deploying', 'running')").Find(&deploys)
}

// todo delete deployment in database
func DeleteModelartsDeploy(jobID string) error {
return deleteModelartsDeploy(x, jobID)
}
func deleteModelartsDeploy(e Engine, jobID string) error {
_, err := e.Where("job_id = ?", jobID).Delete(&ModelartsDeploy{})
return err
}

func DeployStatusConvert(status string) string {
//status, err := GetModelartsDeployStatusByJobID(jobID)
//if err != nil || status == "" {
if status == "" {
return status
} else {
var statusConvert string
switch status {
case "running":
statusConvert = "SUCCEEDED"
case "deploying":
statusConvert = "DEPLOYING"
case "publishing":
statusConvert = "BUILDING"
case "building":
statusConvert = "BUILDING"
case "published":
statusConvert = "WAITING"
case "stopped":
statusConvert = "STOP"
case "stopping":
statusConvert = "STOP"
default:
statusConvert = "FAILED"
}
return statusConvert
}
}

+ 61
- 0
models/modelarts_deploy_queue.go View File

@@ -0,0 +1,61 @@
package models

import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
)

type ModelartsDeployQueue struct {
JobID string `xorm:"pk 'job_id'"`
ModelID string
ModelName string
ServiceID string
DisplayJobName string
CreateUnix timeutil.TimeStamp
}

func CreateModelartsDeployQueue(deploy *ModelartsDeployQueue) (err error) {

sess := x.NewSession()
defer sess.Close()

if err := sess.Begin(); err != nil {
return err
}

if _, err = sess.Insert(deploy); err != nil {
return err
}
return sess.Commit()
}

// write a function to get the deployment by job id
func GetModelartsDeployQueueByJobID(jobID string) (deploy *ModelartsDeployQueue, err error) {
deploy = new(ModelartsDeployQueue)
has, err := x.Where("job_id = ?", jobID).Get(deploy)
if err != nil {
return nil, err
} else if !has {
return nil, ErrModelartsDeployNotExist{jobID}
}
return deploy, nil
}

func GetModelartsDeployQueue(n int) ([]*ModelartsDeployQueue, error) {
return getModelartsDeployQueue(x, n)
}
func getModelartsDeployQueue(e Engine, n int) ([]*ModelartsDeployQueue, error) {
deploys := make([]*ModelartsDeployQueue, 0)
return deploys, e.Asc("create_unix").Limit(n).Find(&deploys)
}

// delete data by jobid
func DeleteModelartsDeployQueueByJobID(jobID string) error {
return deleteModelartsDeployQueueByJobID(x, jobID)
}

func deleteModelartsDeployQueueByJobID(e Engine, jobID string) error {
_, err := e.Where("job_id = ?", jobID).Delete(&ModelartsDeployQueue{})
log.Info("panguService: delte modelarts deploy queue by job id %s", jobID)
return err
}

+ 2
- 0
models/models.go View File

@@ -173,6 +173,8 @@ func init() {
new(AiModelCollect),
new(AiModelFile),
new(ModelMigrateRecord),
new(ModelartsDeploy),
new(ModelartsDeployQueue),
)

tablesStatistic = append(tablesStatistic,


+ 11
- 7
models/resource_scene.go View File

@@ -50,6 +50,7 @@ type SearchResourceSceneOptions struct {
QueueId int64
ComputeResource string
AccCardType string
Cluster string
}

type ResourceSceneListRes struct {
@@ -70,7 +71,7 @@ type ResourceSceneRes struct {
JobType JobType
IsExclusive bool
ExclusiveOrg string
Specs []ResourceSpecWithSceneId
Specs []ResourceSpecInfo
}

func (ResourceSceneRes) TableName() string {
@@ -86,7 +87,7 @@ func (ResourceSceneBriefRes) TableName() string {
return "resource_scene"
}

type ResourceSpecWithSceneId struct {
type ResourceSpecInfo struct {
ID int64
SourceSpecId string
AccCardsNum int
@@ -108,7 +109,7 @@ type ResourceSpecWithSceneId struct {
AccCardType string
}

func (ResourceSpecWithSceneId) TableName() string {
func (ResourceSpecInfo) TableName() string {
return "resource_specification"
}

@@ -264,6 +265,9 @@ func SearchResourceScene(opts SearchResourceSceneOptions) (int64, []ResourceScen
if opts.AccCardType != "" {
cond = cond.And(builder.Eq{"resource_queue.acc_card_type": opts.AccCardType})
}
if opts.Cluster != "" {
cond = cond.And(builder.Eq{"resource_queue.cluster": opts.Cluster})
}
cond = cond.And(builder.NewCond().Or(builder.Eq{"resource_scene.delete_time": 0}).Or(builder.IsNull{"resource_scene.delete_time"}))
cols := []string{"resource_scene.id", "resource_scene.scene_name", "resource_scene.job_type", "resource_scene.is_exclusive",
"resource_scene.exclusive_org"}
@@ -297,7 +301,7 @@ func SearchResourceScene(opts SearchResourceSceneOptions) (int64, []ResourceScen
sceneIds = append(sceneIds, v.ID)
}

specs := make([]ResourceSpecWithSceneId, 0)
specs := make([]ResourceSpecInfo, 0)

if err := x.Cols("resource_specification.id", "resource_specification.source_spec_id",
"resource_specification.acc_cards_num", "resource_specification.cpu_cores",
@@ -316,10 +320,10 @@ func SearchResourceScene(opts SearchResourceSceneOptions) (int64, []ResourceScen
return 0, nil, err
}

specsMap := make(map[int64][]ResourceSpecWithSceneId, 0)
specsMap := make(map[int64][]ResourceSpecInfo, 0)
for _, v := range specs {
if _, ok := specsMap[v.SceneId]; !ok {
specsMap[v.SceneId] = []ResourceSpecWithSceneId{v}
specsMap[v.SceneId] = []ResourceSpecInfo{v}
} else {
specsMap[v.SceneId] = append(specsMap[v.SceneId], v)
}
@@ -328,7 +332,7 @@ func SearchResourceScene(opts SearchResourceSceneOptions) (int64, []ResourceScen
for i, v := range r {
s := specsMap[v.ID]
if s == nil {
s = make([]ResourceSpecWithSceneId, 0)
s = make([]ResourceSpecInfo, 0)
}
r[i].Specs = s
}


+ 28
- 9
models/resource_specification.go View File

@@ -159,6 +159,28 @@ func (r ResourceSpecAndQueue) ConvertToRes() *ResourceSpecAndQueueRes {
}
}

func (r ResourceSpecAndQueue) ConvertToResourceSpecInfo() *ResourceSpecInfo {
return &ResourceSpecInfo{
ID: r.ResourceSpecification.ID,
SourceSpecId: r.SourceSpecId,
AccCardsNum: r.AccCardsNum,
CpuCores: r.CpuCores,
MemGiB: r.MemGiB,
GPUMemGiB: r.GPUMemGiB,
ShareMemGiB: r.ShareMemGiB,
UnitPrice: r.UnitPrice,
Status: r.ResourceSpecification.Status,
UpdatedTime: r.ResourceSpecification.UpdatedTime,
Cluster: r.Cluster,
AiCenterCode: r.AiCenterCode,
AiCenterName: r.AiCenterName,
QueueCode: r.QueueCode,
QueueId: r.QueueId,
ComputeResource: r.ComputeResource,
AccCardType: r.AccCardType,
}
}

type FindSpecsOptions struct {
JobType JobType
ComputeResource string
@@ -209,7 +231,7 @@ func (Specification) TableName() string {
return "resource_specification"
}

func (s *Specification) loadRelatedSpecs() {
func (s *Specification) loadRelatedSpecs(jobType JobType) {
if s.RelatedSpecs != nil {
return
}
@@ -222,8 +244,9 @@ func (s *Specification) loadRelatedSpecs() {
ComputeResource: s.ComputeResource,
Cluster: s.Cluster,
SourceSpecId: s.SourceSpecId,
RequestAll: true,
RequestAll: false,
SpecStatus: SpecOnShelf,
JobType: jobType,
})
if err != nil {
s.RelatedSpecs = defaultSpecs
@@ -231,19 +254,15 @@ func (s *Specification) loadRelatedSpecs() {
}
s.RelatedSpecs = r
}
func (s *Specification) GetAvailableCenterIds(userIds ...int64) []string {
s.loadRelatedSpecs()
func (s *Specification) GetAvailableCenterIds(userId int64, jobType JobType) []string {
s.loadRelatedSpecs(jobType)

if len(s.RelatedSpecs) == 0 {
return make([]string, 0)
}

var uId int64
if len(userIds) > 0 {
uId = userIds[0]
}
//filter exclusive specs
specs := FilterExclusiveSpecs(s.RelatedSpecs, uId)
specs := FilterExclusiveSpecs(s.RelatedSpecs, userId)

centerIds := make([]string, len(specs))
for i, v := range specs {


+ 7
- 0
modules/auth/cloudbrain.go View File

@@ -42,6 +42,13 @@ type CommitImageCloudBrainForm struct {
IsPrivate bool `form:"isPrivate" binding:"Required"`
Topics string `form:"topics"`
}
type CommitImageGrampusForm struct {
Description string `form:"description" binding:"Required"`
Type int `form:"type" binding:"Required"`
Tag string `form:"tag" binding:"Required;MaxSize(50)" `
IsPrivate bool `form:"isPrivate" binding:"Required"`
Topics string `form:"topics"`
}

type CommitAdminImageCloudBrainForm struct {
Description string `form:"description" binding:"Required"`


+ 45
- 0
modules/auth/wechat/finetune.go View File

@@ -0,0 +1,45 @@
package wechat

import (
"fmt"
"time"

"code.gitea.io/gitea/modules/setting"
)

type FinetuneStartMsg struct {
}

var FinetuneMsg = &FinetuneStartMsg{}

func (FinetuneStartMsg) Data(ctx *TemplateContext) *DefaultWechatTemplate {
return &DefaultWechatTemplate{
First: TemplateValue{Value: setting.FineTune.Pangu.Wechat.Title},
Keyword1: TemplateValue{Value: ctx.ModelartsDeploy.DisplayJobName},
Keyword2: TemplateValue{Value: setting.FineTune.Pangu.Wechat.JobType},
Keyword3: TemplateValue{Value: time.Unix(int64(ctx.ModelartsDeploy.UpdateUnix), 0).Format("2006-01-02 15:04:05")},
Remark: TemplateValue{Value: setting.CloudbrainStartedRemark},
}
}

func (FinetuneStartMsg) ShouldSend(ctx *TemplateContext) bool {
return setting.FineTune.Pangu.Wechat.Flag
}

func (FinetuneStartMsg) MsgId(ctx *TemplateContext) string {
return "finetune_start" + "_" + fmt.Sprint(ctx.ModelartsDeploy.JobID)
}

func (FinetuneStartMsg) Url(ctx *TemplateContext) string {
// http://192.168.207.34:8094/extension/modelbase/pangufinetune/inference?jobid=158204&type=0&jobcategory=1
url := setting.AppURL + "extension/modelbase/pangufinetune/inference?"
jobID := "jobid=" + fmt.Sprint(ctx.ModelartsDeploy.JobID)
jobType := "type=" + fmt.Sprint(ctx.ModelartsDeploy.FinetuneModelType)
jobCategory := "jobcategory=" + fmt.Sprint(ctx.ModelartsDeploy.FinetuneCategory)

return url + jobID + "&" + jobType + "&" + jobCategory
}

func (FinetuneStartMsg) TemplateId(ctx *TemplateContext) string {
return setting.FineTune.Pangu.Wechat.TemplateID
}

+ 4
- 2
modules/auth/wechat/template.go View File

@@ -1,11 +1,12 @@
package wechat

import (
"errors"
"fmt"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"errors"
"fmt"
)

type Template interface {
@@ -20,6 +21,7 @@ type TemplateContext struct {
Cloudbrain *models.Cloudbrain
EstimatedEndTime timeutil.TimeStamp
PointAccount *models.PointAccount
ModelartsDeploy *models.ModelartsDeploy
}

func SendTemplateMsg(template Template, ctx *TemplateContext, userId int64) error {


+ 3
- 1
modules/cloudbrain/cloudbrain.go View File

@@ -117,7 +117,9 @@ func CanCreateOrDebugJob(ctx *context.Context) bool {
}

func CanModifyJob(ctx *context.Context, job *models.Cloudbrain) bool {

return isAdminOrJobCreater(ctx, job, nil)
}
func CanDownloadJob(ctx *context.Context, job *models.Cloudbrain) bool {
return isAdminOrJobCreater(ctx, job, nil)
}



+ 17
- 0
modules/convert/cloudbrain.go View File

@@ -104,6 +104,23 @@ func ToSpecification(s *models.Specification) *api.SpecificationShow {
}
}

func ToPointAccount(s *models.PointAccount) *api.PointAccountShow {
if s == nil {
return nil
}
return &api.PointAccountShow{
ID: s.ID,
AccountCode: s.AccountCode,
Balance: s.Balance,
TotalEarned: s.TotalEarned,
TotalConsumed: s.TotalConsumed,
Status: s.Status,
Version: s.Version,
CreatedUnix: int64(s.CreatedUnix),
UpdatedUnix: int64(s.UpdatedUnix),
}
}

func ToTagger(user *models.User) *api.Tagger {
return &api.Tagger{
ID: user.ID,


+ 24
- 0
modules/convert/finetune.go View File

@@ -0,0 +1,24 @@
package convert

import (
"code.gitea.io/gitea/models"
api "code.gitea.io/gitea/modules/structs"
)

func ToFineTuneJobShow(cloudbrain *models.Cloudbrain) *api.FinetuneJobShow {

jobID := cloudbrain.JobID
deployStatus, _ := models.GetModelartsDeployStatusByJobID(jobID)

return &api.FinetuneJobShow{
ID: cloudbrain.ID,
JobID: cloudbrain.JobID,
JobType: cloudbrain.JobType,
DisplayJobName: cloudbrain.DisplayJobName,
Status: cloudbrain.Status,
CreatedUnix: int64(cloudbrain.CreatedUnix),
JobCategory: cloudbrain.FineTuneCategory,
DeployStatus: deployStatus,
Cleared: cloudbrain.Cleared,
}
}

+ 29
- 2
modules/cron/tasks_basic.go View File

@@ -5,11 +5,13 @@
package cron

import (
"code.gitea.io/gitea/services/ai_task_service/schedule"
"code.gitea.io/gitea/services/ai_task_service/task"
"context"
"time"

"code.gitea.io/gitea/routers/api/v1/finetune"
"code.gitea.io/gitea/services/ai_task_service/schedule"
"code.gitea.io/gitea/services/ai_task_service/task"

"code.gitea.io/gitea/modules/setting"

"code.gitea.io/gitea/modules/urfs_client/urchin"
@@ -337,6 +339,28 @@ func registerHandleCloudbrainDurationStatistic() {
})
}

func registerSyncFinetunePanguDeployStatus() {
RegisterTaskFatal("sync_pangu_deploy_status", &BaseConfig{
Enabled: true,
RunAtStart: false,
Schedule: "@every 1m",
}, func(ctx context.Context, _ *models.User, _ Config) error {
finetune.SyncPanguDeployStatus()
return nil
})
}

func registerFinetunePanguServiceCreateQueue() {
RegisterTaskFatal("pangu_service_create_queue", &BaseConfig{
Enabled: true,
RunAtStart: false,
Schedule: "@every 5m",
}, func(ctx context.Context, _ *models.User, _ Config) error {
finetune.PanguServiceCreateQueue()
return nil
})
}

func initBasicTasks() {
registerUpdateMirrorTask()
registerRepoHealthCheck()
@@ -372,4 +396,7 @@ func initBasicTasks() {
registerHandleModelMigrateRecord()
//registerSyncAITaskStatus()
registerHandleNoJobIdAITasks()

registerSyncFinetunePanguDeployStatus()
registerFinetunePanguServiceCreateQueue()
}

+ 24
- 21
modules/grampus/grampus.go View File

@@ -78,18 +78,20 @@ type GenerateTrainJobReq struct {
ComputeResource string
ProcessType string

DatasetNames string
DatasetInfos map[string]models.DatasetInfo
Params string
ModelName string
LabelName string
CkptName string
ModelId string
ModelVersion string
PreTrainModelPath string
PreTrainModelUrl string
Spec *models.Specification
CodeName string
DatasetNames string
DatasetInfos map[string]models.DatasetInfo
Params string
ModelName string
LabelName string
CkptName string
ModelId string
ModelVersion string
PreTrainModelPath string
PreTrainModelUrl string
Spec *models.Specification
CodeName string
PreTrainModelPaths []string
CkptNames []string
}

type GenerateNotebookJobReq struct {
@@ -275,7 +277,7 @@ func GenerateNotebookJob(ctx *context.Context, req *GenerateNotebookJobReq) (job
AutoStopDuration: autoStopDurationMs,
Capacity: setting.Capacity,
Command: req.Command,
CenterID: req.Spec.GetAvailableCenterIds(ctx.User.ID),
CenterID: req.Spec.GetAvailableCenterIds(ctx.User.ID, models.JobTypeDebug),
},
},
})
@@ -344,18 +346,19 @@ func GenerateTrainJob(ctx *context.Context, req *GenerateTrainJobReq) (jobId str
createTime := timeutil.TimeStampNow()

var datasetGrampus, modelGrampus []models.GrampusDataset
var codeGrampus, outputGrampus models.GrampusDataset
var ckptGrampus, codeGrampus, outputGrampus models.GrampusDataset
if ProcessorTypeNPU == req.ProcessType {
datasetGrampus = getDatasetGrampus(req.DatasetInfos)
if len(req.ModelName) != 0 {
modelGrampus = []models.GrampusDataset{
{
Name: req.ModelName,
for i, ckptName := range req.CkptNames {
if len(req.CkptNames) != 0 {
ckptGrampus = models.GrampusDataset{
Name: ckptName,
Bucket: setting.Bucket,
EndPoint: getEndPoint(),
ObjectKey: req.PreTrainModelPath,
},
ObjectKey: req.PreTrainModelPaths[i],
}
}
modelGrampus = append(modelGrampus, ckptGrampus)
}
codeGrampus = models.GrampusDataset{
Name: req.CodeName,
@@ -433,7 +436,7 @@ func GenerateTrainJob(ctx *context.Context, req *GenerateTrainJobReq) (jobId str
ResourceSpecId: req.Spec.SourceSpecId,
ImageId: req.ImageId,
ImageUrl: req.ImageUrl,
CenterID: req.Spec.GetAvailableCenterIds(ctx.User.ID),
CenterID: req.Spec.GetAvailableCenterIds(ctx.User.ID, models.JobTypeTrain),
ReplicaNum: 1,
Datasets: datasetGrampus,
Models: modelGrampus,


+ 144
- 0
modules/modelarts/modelarts.go View File

@@ -39,6 +39,7 @@ const (
MultiDataUrl = "multi_data_url"
ResultUrl = "result_url"
CkptUrl = "ckpt_url"
PretrainUrl = "pretrain_url"
DeviceTarget = "device_target"
Ascend = "Ascend"
PerPage = 10
@@ -94,6 +95,9 @@ type GenerateTrainJobReq struct {
ModelId string
ModelVersion string
PreTrainModelUrl string
FineTune bool
FineTuneModelType int
FineTuneCategory int
}

type GenerateInferenceJobReq struct {
@@ -132,6 +136,29 @@ type GenerateInferenceJobReq struct {
UserCommand string
}

type GenerateDeployModelReq struct {
JobID string
ModelName string
ModelVersion string
ModelType string
SourceLocation string
UserID int64
Runtime string
InstallType []string
}

type GenerateDeployServiceReq struct {
JobID string
InferType string
ServiceName string
Spec string
Duration int
TimeUnit string
ScheduleType string
ModelID string
InstanceCount int
}

type VersionInfo struct {
Version []struct {
ID int `json:"id"`
@@ -370,6 +397,9 @@ func GenerateTrainJob(ctx *context.Context, req *GenerateTrainJobReq) (jobId str
PreTrainModelUrl: req.PreTrainModelUrl,
CkptName: req.CkptName,
ModelId: req.ModelId,
FineTune: req.FineTune,
FineTuneModelType: req.FineTuneModelType,
FineTuneCategory: req.FineTuneCategory,
})

if createErr != nil {
@@ -1223,3 +1253,117 @@ func handleTempTrainJob(temp *models.CloudbrainTemp) error {

return err
}

func GenerateDeployModel(ctx *context.Context, req *GenerateDeployModelReq) (string, error) {
createTime := timeutil.TimeStampNow()
deploy, _ := models.GetModelartsDeployByJobID(req.JobID)

var deployModelResult *models.CreateDeployModelResult
var createErr error
deployModelResult, createErr = createDeployModel(models.CreateDeployModelParams{
ModelName: req.ModelName,
ModelVersion: req.ModelVersion,
ModelType: req.ModelType,
SourceLocation: req.SourceLocation,
Runtime: req.Runtime,
InstallType: req.InstallType,
Prebuild: true,
})
if createErr != nil {
deploy.UpdateUnix = createTime
deploy.Status = "FAILED"
err := models.UpdateDeploy(deploy)
if err != nil {
log.Error("CreateDeployModel DB(%s) failed:%v", deployModelResult.ModelID, createErr.Error())
return "", err
}
log.Error("createDeployModelAPI failed: %v", createErr.Error())
return "", createErr
}

deploy.ModelID = deployModelResult.ModelID
deploy.ModelType = req.ModelType
deploy.ModelName = req.ModelName
deploy.UpdateUnix = createTime
deploy.ModelStatus = "publishing"
deploy.Status = "BUILDING"
err := models.UpdateDeploy(deploy)
if err != nil {
log.Error("CreateDeployModel DB(%s) failed:%v", deployModelResult.ModelID, createErr.Error())
return "", err
}

return deployModelResult.ModelID, nil
}

func GenerateDeployService(req *GenerateDeployServiceReq) (string, error) {
createTime := timeutil.TimeStampNow()
deploy, _ := models.GetModelartsDeployByJobID(req.JobID)

var deployServiceResult *models.CreateDeployServiceResult
var createErr error
deployServiceResult, createErr = createDeployService(models.CreateDeployServiceParams{
InferType: req.InferType,
ServiceName: req.ServiceName,
ClusterID: setting.FineTune.Pangu.Service.ClusterID,
Config: []models.ServiceConfig{
{
Specification: req.Spec,
ModelID: req.ModelID,
InstanceCount: req.InstanceCount,
// CustomSpec: models.ServiceCustomSpec{
// CPU: setting.FineTune.Pangu.Service.CPU,
// Memory: setting.FineTune.Pangu.Service.Memory,
// Ascend: setting.FineTune.Pangu.Service.NPU,
// CPUInfo: models.CPUInfo{
// CPU: setting.FineTune.Pangu.Service.CPU,
// Arch: setting.FineTune.Pangu.Service.CPUArch,
// },
// MemoryInfo: models.MemoryInfo{
// Memory: setting.FineTune.Pangu.Service.MemoryInfo,
// Unit: setting.FineTune.Pangu.Service.MemoryUnit,
// },
// NPUInfo: models.NPUInfo{
// NPU: setting.FineTune.Pangu.Service.NPU,
// Brand: setting.FineTune.Pangu.Service.NPUBrand,
// Version: setting.FineTune.Pangu.Service.NPUVersion,
// Unit: setting.FineTune.Pangu.Service.NPUMemoryUnit,
// Memory: setting.FineTune.Pangu.Service.NPUMemory,
// },
// },
},
},
Schedule: []models.DeploySchedule{
{
Duration: req.Duration,
TimeUnit: req.TimeUnit,
Type: req.ScheduleType,
},
},
})

if createErr != nil {
log.Error("createDeployServiceAPI failed: %v", createErr.Error())
deploy.UpdateUnix = createTime
deploy.Status = "FAILED"
err := models.UpdateDeploy(deploy)
if err != nil {
log.Error("CreateDeployService DB(%s) failed:%v", deployServiceResult.ServiceID, createErr.Error())
return "", err
}
return "", createErr
}

deploy.ServiceID = deployServiceResult.ServiceID
deploy.ServiceName = req.ServiceName
deploy.UpdateUnix = createTime
deploy.ServiceStatus = "deploying"
deploy.Status = "DEPLOYING"
err := models.UpdateDeploy(deploy)
if err != nil {
log.Error("CreateDeployService DB(%s) failed:%v", deployServiceResult.ServiceID, createErr.Error())
return "", err
}

return deployServiceResult.ServiceID, nil
}

+ 364
- 0
modules/modelarts/resty.go View File

@@ -27,6 +27,8 @@ const (
urlTrainJob = "/training-jobs"
urlResourceSpecs = "/job/resource-specs"
urlTrainJobConfig = "/training-job-configs"
urlDeployModel = "/models"
urlDeployService = "/services"
errorCodeExceedLimit = "ModelArts.0118"

//notebook 2.0
@@ -1033,6 +1035,17 @@ func DelTrainJob(jobID string) (*models.TrainJobResult, error) {
client := getRestyClient()
var result models.TrainJobResult

//get cloudbrain job by jobid
finetuneJob, _ := models.GetCloudbrainByJobID(jobID)
log.Info("调试:%s", finetuneJob.FineTune)
if finetuneJob.FineTune {
err := ServiceDelete(jobID)
if err != nil {
log.Error("盘古微调部署: Delete Deploy failed:%s %v", jobID, err.Error())
return &result, err
}
}

retry := 0

sendjob:
@@ -1131,6 +1144,17 @@ func DelTrainJobVersion(jobID string, versionID string) (*models.TrainJobResult,
client := getRestyClient()
var result models.TrainJobResult

//get cloudbrain job by jobid
finetuneJob, _ := models.GetCloudbrainByJobID(jobID)
log.Info("调试:%s", finetuneJob.FineTune)
if finetuneJob.FineTune {
err := ServiceDelete(jobID)
if err != nil {
log.Error("盘古微调部署: Delete Deploy failed:%s %v", jobID, err.Error())
return &result, err
}
}

retry := 0

sendjob:
@@ -1524,3 +1548,343 @@ sendjob:

return &result, nil
}

func createDeployModel(createDeployModelParams models.CreateDeployModelParams) (*models.CreateDeployModelResult, error) {
checkSetting()
client := getRestyClient()
var result models.CreateDeployModelResult

retry := 0
req, _ := json.Marshal(createDeployModelParams)
log.Info("postapi json: %s", req)

sendjob:
res, err := client.R().
SetHeader("Content-Type", "application/json").
SetAuthToken(TOKEN).
SetBody(createDeployModelParams).
SetResult(&result).
Post(HOST + "/v1/" + setting.ProjectID + urlDeployModel)

if err != nil {
return nil, fmt.Errorf("resty create deploy model: %s", err)
}

if res.StatusCode() == http.StatusUnauthorized && retry < 1 {
retry++
_ = getToken()
goto sendjob
}

if res.StatusCode() != http.StatusOK {
var temp models.ErrorResult
if err = json.Unmarshal([]byte(res.String()), &temp); err != nil {
log.Error("json.Unmarshal failed(%s): %v", res.String(), err.Error())
return &result, fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error())
}
log.Error("createDeployModel failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
if res.StatusCode() == http.StatusBadGateway {
return &result, fmt.Errorf(UnknownErrorPrefix+"createDeployModel failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
} else {
return &result, fmt.Errorf("createDeployModel failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
}
}

return &result, nil
}

func GetDeployModel(modelID string) (*models.GetDeployModelResult, error) {
checkSetting()
client := getRestyClient()
var result models.GetDeployModelResult

retry := 0

sendjob:
//https://modelarts.cn-east-3.myhuaweicloud.com/v1/{project_id}/models/{model_id}
res, err := client.R().
SetAuthToken(TOKEN).
SetResult(&result).
Get(HOST + "/v1/" + setting.ProjectID + urlDeployModel + "/" + modelID)

if err != nil {
return nil, fmt.Errorf("resty GetDeployModel: %v", err)
}

if res.StatusCode() == http.StatusUnauthorized && retry < 1 {
retry++
_ = getToken()
goto sendjob
}

if res.StatusCode() != http.StatusOK {
var temp models.ErrorResult
if err = json.Unmarshal([]byte(res.String()), &temp); err != nil {
log.Error("json.Unmarshal failed(%s): %v", res.String(), err.Error())
return &result, fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error())
}
log.Error("GetTrainJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
return &result, fmt.Errorf("获取部署模型失败(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
}

return &result, nil
}

func createDeployService(CreateDeployServiceParams models.CreateDeployServiceParams) (*models.CreateDeployServiceResult, error) {
checkSetting()
client := getRestyClient()
var result models.CreateDeployServiceResult

retry := 0
req, _ := json.Marshal(CreateDeployServiceParams)
log.Info("postapi json: %s", req)

sendjob:
res, err := client.R().
SetHeader("Content-Type", "application/json").
SetAuthToken(TOKEN).
SetBody(CreateDeployServiceParams).
SetResult(&result).
Post(HOST + "/v1/" + setting.ProjectID + urlDeployService)

if err != nil {
return nil, fmt.Errorf("resty create deploy model: %s", err)
}

if res.StatusCode() == http.StatusUnauthorized && retry < 1 {
retry++
_ = getToken()
goto sendjob
}

if res.StatusCode() != http.StatusOK {
var temp models.ErrorResult
if err = json.Unmarshal([]byte(res.String()), &temp); err != nil {
log.Error("json.Unmarshal failed(%s): %v", res.String(), err.Error())
return &result, fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error())
}
log.Error("createDeployModel failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
if res.StatusCode() == http.StatusBadGateway {
return &result, fmt.Errorf(UnknownErrorPrefix+"createDeployModel failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
} else {
return &result, fmt.Errorf("createDeployModel failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
}
}

return &result, nil
}

func GetDeployService(serviceID string) (*models.GetDeployServiceResult, error) {
checkSetting()
client := getRestyClient()
var result models.GetDeployServiceResult

retry := 0

sendjob:
//https://modelarts.cn-east-3.myhuaweicloud.com/v1/{project_id}/models/{model_id}
res, err := client.R().
SetAuthToken(TOKEN).
SetResult(&result).
Get(HOST + "/v1/" + setting.ProjectID + urlDeployService + "/" + serviceID)

if err != nil {
return nil, fmt.Errorf("resty GetDeployService: %v", err)
}

if res.StatusCode() == http.StatusUnauthorized && retry < 1 {
retry++
_ = getToken()
goto sendjob
}

if res.StatusCode() != http.StatusOK {
var temp models.ErrorResult
if err = json.Unmarshal([]byte(res.String()), &temp); err != nil {
log.Error("json.Unmarshal failed(%s): %v", res.String(), err.Error())
return &result, fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error())
}
log.Error("GetTrainJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
return &result, fmt.Errorf("获取部署模型失败(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
}

return &result, nil
}

func DeleteDeployModel(modelID string) error {
checkSetting()
client := getRestyClient()
retry := 0

sendjob:
res, err := client.R().
SetHeader("Content-Type", "application/json").
SetAuthToken(TOKEN).
Delete(HOST + "/v1/" + setting.ProjectID + urlDeployModel + "/" + modelID)

if res.StatusCode() == http.StatusUnauthorized && retry < 1 {
retry++
_ = getToken()
goto sendjob
}

if res.StatusCode() != http.StatusOK {
var temp models.ErrorResult
if err = json.Unmarshal([]byte(res.String()), &temp); err != nil {
log.Error("json.Unmarshal failed(%s): %v", res.String(), err.Error())
return fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error())
}
log.Error("DelTrainJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
return fmt.Errorf("删除部署模型失败(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
}

return nil
}

func DeleteDeployService(serviceID string) error {
checkSetting()
client := getRestyClient()

retry := 0

sendjob:
res, err := client.R().
SetHeader("Content-Type", "application/json").
SetAuthToken(TOKEN).
Delete(HOST + "/v1/" + setting.ProjectID + urlDeployService + "/" + serviceID)

if err != nil {
return fmt.Errorf("resty DelDeployService: %v", err)
}

if res.StatusCode() == http.StatusUnauthorized && retry < 1 {
retry++
_ = getToken()
goto sendjob
}

if res.StatusCode() != http.StatusOK {
var temp models.ErrorResult
if err = json.Unmarshal([]byte(res.String()), &temp); err != nil {
log.Error("json.Unmarshal failed(%s): %v", res.String(), err.Error())
return fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error())
}
log.Error("DelTrainJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
return fmt.Errorf("删除部署任务失败(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg)
}

return nil
}

func UpdateDeployService(serviceID string, UpdateDeployServiceParams models.UpdateDeployServiceParams) error {
checkSetting()
client := getRestyClient()

retry := 0

sendjob:
res, err := client.R().
SetHeader("Content-Type", "application/json").
SetAuthToken(TOKEN).
SetBody(UpdateDeployServiceParams).
Put(HOST + "/v1/" + setting.ProjectID + urlDeployService + "/" + serviceID)

if res.StatusCode() == http.StatusUnauthorized && retry < 1 {
retry++
_ = getToken()
goto sendjob
}

if res.StatusCode() != http.StatusOK {
var temp models.ErrorResult
if err = json.Unmarshal([]byte(res.String()), &temp); err != nil {
log.Error("json.Unmarshal failed(%s): %v", res.String(), err.Error())
//return &result, fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error())
}
}

return nil
}

// https://modelarts-inference.cloudbrain2.pcl.ac.cn/v1/infers/cc1e11c5-16a0-4202-a031-819a8e178a0d
func SendInferenceDeploy(inferAddr string, text string) (*models.PanguInferResult, error) {
panguInferParams := &models.PanguInferParams{
Text: text,
}
checkSetting()
client := getRestyClient()
var result models.PanguInferResult

retry := 0
req, _ := json.Marshal(panguInferParams)
log.Info("盘古微调部署:postapi json: ", req)
log.Info("盘古微调部署 推理地址: ", inferAddr)
log.Info("盘古微调部署 推理token: ", TOKEN)

sendjob:
res, err := client.R().
SetHeader("Content-Type", "application/json").
SetHeader("X-Auth-Token", TOKEN).
SetAuthToken(TOKEN).
SetBody(panguInferParams).
SetResult(&result).
Post(inferAddr)
//Post(HOST + "/v1/" + urlDeployInfer + serviceID)

if err != nil {
return nil, fmt.Errorf("resty post text inference failed: %s", err)
}

if res.StatusCode() == http.StatusUnauthorized && retry < 1 {
retry++
_ = getToken()
goto sendjob
}

if res.StatusCode() != http.StatusOK {
var erroResult models.PanguInferError
if err = json.Unmarshal([]byte(res.String()), &erroResult); err != nil {
log.Error("json.Unmarshal failed(%s): %v", res.String(), err.Error())
}
log.Error("盘古微调部署 inference failed(%d):%s", res.StatusCode(), res.String())
log.Error("盘古微调部署 %s, %s", erroResult.ErrorCode, erroResult.ErrorMsg)
return &result, fmt.Errorf("盘古微调部署 部署任务推理失败(%d): %s", res.StatusCode(), erroResult.ErrorMsg)
}
log.Info("盘古微调部署 推理返回状态码: %s", res.StatusCode())
log.Info("盘古微调部署 推理结果: %s", result)

return &result, nil
}

func ServiceDelete(jobID string) error {
if deploy, _ := models.GetModelartsDeployByJobID(jobID); deploy != nil {
if deploy.Status == "STOP" || deploy.Status == "FAILED" {
if deploy.ServiceID != "" {
err := DeleteDeployService(deploy.ServiceID)
if err != nil {
log.Error("panguService: Delete DeployService API failed:%s %v", jobID, err.Error())
return err
}
log.Info("panguService: deploy service delete success %s", jobID)
}
if deploy.ModelID != "" {
err := DeleteDeployModel(deploy.ModelID)
if err != nil {
log.Error("panguService: Delete DeployModel API failed:%s %v", jobID, err.Error())
return err
}
log.Info("panguService: deploy model delete success %s", jobID)
}
err := models.DeleteModelartsDeploy(jobID)
if err != nil {
log.Error("panguService: Delete ModelartsDeploy from DB failed:%s %v", jobID, err.Error())
return err
}
log.Info("panguService: deploy DB record delete success %s", jobID)
} else {
log.Error("the job(%s) is a deploying finetune job, can be not deleted", jobID)
return fmt.Errorf("1")
}
}
return nil
}

+ 1
- 0
modules/notification/base/notifier.go View File

@@ -66,4 +66,5 @@ type Notifier interface {

NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain, oldStatus string)
NotifyCloudbrainTaskComingToFinished(cloudbrain *models.Cloudbrain, endTime timeutil.TimeStamp, account *models.PointAccount)
NotifyChangeFinetuneStatus(deployment *models.ModelartsDeploy)
}

+ 4
- 0
modules/notification/base/null.go View File

@@ -185,3 +185,7 @@ func (*NullNotifier) NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain,
func (*NullNotifier) NotifyCloudbrainTaskComingToFinished(cloudbrain *models.Cloudbrain, endTime timeutil.TimeStamp, account *models.PointAccount) {

}

func (*NullNotifier) NotifyChangeFinetuneStatus(deployment *models.ModelartsDeploy) {

}

+ 7
- 0
modules/notification/notification.go View File

@@ -326,3 +326,10 @@ func NotifyCloudbrainTaskComingToFinished(cloudbrain *models.Cloudbrain, estimat
notifier.NotifyCloudbrainTaskComingToFinished(cloudbrain, estimatedEndTime, account)
}
}

// NotifyChangeFinetuneDeployStatus
func NotifyChangeFinetuneStatus(deployment *models.ModelartsDeploy) {
for _, notifier := range notifiers {
notifier.NotifyChangeFinetuneStatus(deployment)
}
}

+ 6
- 0
modules/notification/wechat/wechat.go View File

@@ -41,3 +41,9 @@ func (*wechatNotifier) NotifyCloudbrainTaskComingToFinished(cloudbrain *models.C
template := wechat.ComingStopMsg
go wechat.SendTemplateMsg(template, &wechat.TemplateContext{Cloudbrain: cloudbrain, EstimatedEndTime: endTime, PointAccount: account}, cloudbrain.UserID)
}

func (*wechatNotifier) NotifyChangeFinetuneStatus(deployment *models.ModelartsDeploy) {
log.Info("盘古微调部署: NotifyChangeFineTuneStatus deployment.jobid=%d deployment.status=%s", deployment.JobID, deployment.Status)
template := wechat.FinetuneMsg
go wechat.SendTemplateMsg(template, &wechat.TemplateContext{ModelartsDeploy: deployment}, deployment.UserID)
}

+ 105
- 0
modules/setting/finetune.go View File

@@ -0,0 +1,105 @@
package setting

import (
"encoding/json"

"code.gitea.io/gitea/modules/log"
)

type PanguFineTune struct {
Basic struct {
OwnerName string `json:"owner_name"`
RepoName string `json:"repo_name"`
BootFile string `json:"boot_file"`
BranchName string `json:"branch_name"`
ImageId int64 `json:"image_id"`
Image string `json:"image"`
}
Dataset struct {
Attachments []string `json:"attachments"`
DatasetNames []string `json:"dataset_names"`
}

Model struct {
ModelNames []string `json:"model_names"`
ModelAttachments []string `json:"model_attachments"`
}
Deploy struct {
CodeBucket string `json:"code_bucket"`
CodeObsPath string `json:"code_obs_path"`
ModelName string `json:"deploy_model_name"`
Runtime string `json:"runtime"`
MaxDeployNum int `json:"max_deploy_num"`
MaxDeployPerUser int `json:"max_deploy_per_user"`
Duration int `json:"duration"`
WarmupDuration int `json:"warmup_duration"`
TimeUnit string `json:"time_unit"`
Specification string `json:"specification"`
}
Service struct {
ClusterID string `json:"cluster_id"`
CPU int `json:"cpu"`
CPUArch string `json:"cpu_arch"`
Memory int `json:"memory"`
MemoryInfo int `json:"memory_info"`
MemoryUnit string `json:"memory_unit"`
NPU int `json:"npu"`
NPUBrand string `json:"npu_brand"`
NPUVersion string `json:"npu_version"`
NPUMemory int `json:"npu_memory"`
NPUMemoryUnit string `json:"npu_memory_unit"`
}
Wechat struct {
Flag bool `json:"flag"`
Title string `json:"title"`
JobType string `json:"job_type"`
TemplateID string `json:"template_id"`
}
}

var FineTune = struct {
MaxJobNum int
Pangu *PanguFineTune
}{
MaxJobNum: 5,
Pangu: &PanguFineTune{},
}

func getFineTuneConfig() {
sec := Cfg.Section("finetune")
FineTune.MaxJobNum = sec.Key("MAX_JOB_NUM").MustInt(5)
panguBasicConfig := sec.Key("pangu_basic").MustString("")

if err := json.Unmarshal([]byte(panguBasicConfig), &FineTune.Pangu.Basic); err != nil {
log.Error("Unmarshal(panguConfig) failed:%v", err)
}
panguDatasetConfig := sec.Key("pangu_dataset").MustString("")

if err := json.Unmarshal([]byte(panguDatasetConfig), &FineTune.Pangu.Dataset); err != nil {
log.Error("Unmarshal(panguConfig) failed:%v", err)
}

panguModelConfig := sec.Key("pangu_model").MustString("")

if err := json.Unmarshal([]byte(panguModelConfig), &FineTune.Pangu.Model); err != nil {
log.Error("Unmarshal(panguConfig) failed:%v", err)
}

panguDeployConfig := sec.Key("pangu_deploy").MustString("")

if err := json.Unmarshal([]byte(panguDeployConfig), &FineTune.Pangu.Deploy); err != nil {
log.Error("Unmarshal(panguConfig) failed:%v", err)
}

panguServiceConfig := sec.Key("pangu_service").MustString("")

if err := json.Unmarshal([]byte(panguServiceConfig), &FineTune.Pangu.Service); err != nil {
log.Error("Unmarshal(panguConfig) failed:%v", err)
}

panguWechatConfig := sec.Key("pangu_wechat").MustString("")

if err := json.Unmarshal([]byte(panguWechatConfig), &FineTune.Pangu.Wechat); err != nil {
log.Error("Unmarshal(panguConfig) failed:%v", err)
}
}

+ 1
- 0
modules/setting/setting.go View File

@@ -1748,6 +1748,7 @@ func NewContext() {
getModelSafetyConfig()
getModelAppConfig()
getClearStrategy()
getFineTuneConfig()
}

func getModelSafetyConfig() {


+ 60
- 18
modules/structs/cloudbrain.go View File

@@ -22,25 +22,56 @@ type CreateGrampusTrainJobOption struct {
SpecId int64 `json:"spec_id" binding:"Required"`
}

type CreateFineTuneJobOption struct {
Type int `json:"type"`
DisplayJobName string `json:"display_job_name" binding:"Required"`
//上传数据集和选择平台数据集需要输入
Attachment string `json:"attachment"`
DatasetName string `json:"dataset_name"`
//选择示例数据集必填
SampleDatasetType int `json:"sample_dataset_type"`
Description string `json:"description" `
Params string `json:"run_para_list" binding:"Required"`
SpecId int64 `json:"spec_id" binding:"Required"`
}

type CreateFineTuneDeployOption struct {
JobID string `json:"job_id" binding:"Required"`
}

type UpdateFineTuneDeployOption struct {
JobID string `json:"job_id" binding:"Required"`
Status string `json:"status" binding:"Required"`
}

type PanguInferenceOption struct {
JobID string `json:"job_id" binding:"Required"`
Text string `json:"text" binding:"Required"`
}

type CreateTrainJobOption struct {
Type int `json:"type"`
DisplayJobName string `json:"display_job_name" binding:"Required"`
ImageID string `json:"image_id"`
Image string `json:"image" binding:"Required"`
Attachment string `json:"attachment" binding:"Required"`
DatasetName string `json:"dataset_name" binding:"Required"`
Description string `json:"description" `
BootFile string `json:"boot_file" binding:"Required"`
BranchName string `json:"branch_name" binding:"Required"`
Params string `json:"run_para_list" binding:"Required"`
WorkServerNumber int `json:"work_server_number"`
ModelName string `json:"model_name"`
ModelVersion string `json:"model_version"`
CkptName string `json:"ckpt_name"`
ModelId string `json:"model_id"`
LabelName string `json:"label_names"`
PreTrainModelUrl string `json:"pre_train_model_url"`
SpecId int64 `json:"spec_id" binding:"Required"`
Type int `json:"type"`
DisplayJobName string `json:"display_job_name" binding:"Required"`
ImageID string `json:"image_id"`
Image string `json:"image" binding:"Required"`
Attachment string `json:"attachment" binding:"Required"`
DatasetName string `json:"dataset_name" binding:"Required"`
Description string `json:"description" `
BootFile string `json:"boot_file" binding:"Required"`
BranchName string `json:"branch_name" binding:"Required"`
Params string `json:"run_para_list" binding:"Required"`
WorkServerNumber int `json:"work_server_number"`
ModelName string `json:"model_name"`
ModelVersion string `json:"model_version"`
CkptName string `json:"ckpt_name"`
ModelId string `json:"model_id"`
LabelName string `json:"label_names"`
PreTrainModelUrl string `json:"pre_train_model_url"`
SpecId int64 `json:"spec_id" binding:"Required"`
FineTune bool `json:"-"`
FineTuneModelType int `json:"-"`
FineTuneCategory int `json:"-"`
CodeRepo interface{} `json:"-"`
}
type CreateNotebookOption struct {
Type int `json:"type"`
@@ -112,3 +143,14 @@ type SpecificationShow struct {
ComputeResource string `json:"compute_resource"`
UnitPrice int `json:"unit_price"`
}
type PointAccountShow struct {
ID int64 `json:"id"`
AccountCode string `json:"account_code"`
Balance int64 `json:"balance"`
TotalEarned int64 `json:"total_earned"`
TotalConsumed int64 `json:"total_consumed"`
Status int `json:"status"`
Version int64 `json:"version"`
CreatedUnix int64 `json:"created_unix"`
UpdatedUnix int64 `json:"updated_unix"`
}

+ 14
- 0
modules/structs/finetune.go View File

@@ -0,0 +1,14 @@
package structs

type FinetuneJobShow struct {
ID int64 `json:"id"`
JobID string `json:"job_id"`
JobType string `json:"job_type"`
Type int `json:"type"`
DisplayJobName string `json:"display_job_name"`
Status string `json:"status"`
CreatedUnix int64 `json:"created_unix"`
JobCategory int `json:"job_category"`
DeployStatus string `json:"deploy_status"`
Cleared bool `json:"cleared"`
}

+ 35
- 11
options/locale/locale_en-US.ini View File

@@ -994,6 +994,11 @@ favorite=Like
disassociate=Disassociate
benchmark_dataset_tip=Note: first use the dataset function to upload the model, and then select the model from the dataset list.
file_deleted=The file has been deleted
select_result_file= Select result file
select_file = Select file
export_file = Export
go_new_dataset= to create the dataset
export_tips = Only <span style="color:red">zip/tar.gz</span> type result file export is supported, and the exported file can finally be viewed on the Datasets tab of the current project.

[repo]
owner = Owner
@@ -1061,6 +1066,7 @@ cloudbrain.time.starttime=Start run time
cloudbrain.time.endtime=End run time
cloudbrain.datasetdownload=Dataset download url
model_manager = Model
model_base = Large Model Base
model_square = Model Square
model_experience = Model Experience
model_noright=You have no right to do the operation.
@@ -1071,7 +1077,7 @@ notebook_select_wrong=Please select a Notebook(.ipynb) file first.
notebook_path_too_long=The total length of selected file or files path exceed 255 characters, please select a shorter path file or change the file path.
notebook_branch_name_too_long=The total length of branch or branches name exceed 255 characters, please select a file in other branch.
notebook_file_no_right=You have no right to access the Notebook(.ipynb) file.
notebook_branch_name_not_support=The branch name contains < > ' " ; \ ` = # $ % ^ ( ), can not run online.
notebook_branch_name_not_support=The branch name contains < > ' " ; \ ` = # $ % ^ ( ), can not run online'.
debug_again_fail=Fail to restart debug task, please try again later.
debug_again_fail_forever=The task was scheduled failed last time, can not restart.

@@ -1111,9 +1117,13 @@ images.name = Image Tag
images.name_placerholder = Please enter the image name
image.label_tooltips = Example Python 3.7, Tensorflow 2.0, cuda 10, pytorch 1.6
images.public_tooltips = After the image is set to public, it can be seen by other users.
images.name_rule = Please enter letters, numbers, _ and - up to 64 characters and cannot end with a dash (-).
images.name_format_err=The format of image tag is wrong.
images.name_rule50 = Please enter letters, numbers, _ and - up to 50 characters and starts with a letter.
images.name_rule100 = Please enter letters, numbers, _ and - up to 100 characters and cannot end with a dash (-).
images.delete_task = Delete image
images.task_delete_confirm = Are you sure you want to delete this image? Once this image is deleted, it cannot be recovered.
export_result_to_dataset = Export the results to a dataset
loader_result_file = Loading results file...

cloudbrain=Cloudbrain
cloudbrain.task = Cloudbrain Task
@@ -1318,7 +1328,7 @@ model.manage.import_online_model=Import Online Model
model.manage.notcreatemodel=No model has been created
model.manage.init1=Code version: You have not initialized the code repository, please
model.manage.init2=initialized first ;
model.manage.createtrainjob_tip=Training task: you haven't created a training task, please create it first
model.manage.createtrainjob_tip=Training task: you have not created a training task, please create it first
model.manage.createmodel_tip=You can import local model or online model. Import online model should
model.manage.createtrainjob=Create training task.
model.manage.delete=Delete Model
@@ -1541,7 +1551,7 @@ editor.file_deleting_no_longer_exists = The file being deleted, '%s', no longer
editor.file_changed_while_editing = The file contents have changed since you started editing. <a target="_blank" rel="noopener noreferrer" href="%s">Click here</a> to see them or <strong>Commit Changes again</strong> to overwrite them.
editor.file_already_exists = A file named '%s' already exists in this repository.
editor.commit_empty_file_header = Commit an empty file
editor.commit_empty_file_text = The file you're about commit is empty. Proceed?
editor.commit_empty_file_text = The file you are about commit is empty. Proceed?
editor.no_changes_to_show = There are no changes to show.
editor.fail_to_update_file = Failed to update/create file '%s' with error: %v
editor.push_rejected_no_message = The change was rejected by the server without a message. Please check githooks.
@@ -1557,7 +1567,7 @@ editor.require_signed_commit = Branch requires a signed commit
editor.repo_too_large = Repository can not exceed %d MB
editor.repo_file_invalid = Upload files are invalid
editor.upload_file_too_much = Can not upload more than %d files at a time
editor.rename = rename "%s" to %s"
editor.rename = rename "%s" to "%s"
editor.file_changed_while_renaming=The version of the file or folder to be renamed has changed. Please refresh the page and try again


@@ -1717,7 +1727,7 @@ issues.collaborator = Collaborator
issues.owner = Owner
issues.re_request_review=Re-request review
issues.remove_request_review=Remove review request
issues.remove_request_review_block=Can't remove review request
issues.remove_request_review_block=Can not remove review request
issues.sign_in_require_desc = <a href="%s">Sign in</a> to join this conversation.
issues.edit = Edit
issues.cancel = Cancel
@@ -1873,7 +1883,7 @@ pulls.merged_as = The pull request has been merged as <a rel="nofollow" class="u
pulls.is_closed = The pull request has been closed.
pulls.has_merged = The pull request has been merged.
pulls.title_wip_desc = `<a href="#">Start the title with <strong>%s</strong></a> to prevent the pull request from being merged accidentally.`
pulls.cannot_merge_work_in_progress = This pull request is marked as a work in progress. Remove the <strong>%s</strong> prefix from the title when it's ready
pulls.cannot_merge_work_in_progress = This pull request is marked as a work in progress. Remove the <strong>%s</strong> prefix from the title when it is ready
pulls.data_broken = This pull request is broken due to missing fork information.
pulls.files_conflicted = This pull request has changes conflicting with the target branch.
pulls.is_checking = "Merge conflict checking is in progress. Try again in few moments."
@@ -3235,10 +3245,10 @@ mark_all_as_read = Mark all as read
default_key=Signed with default key
error.extract_sign = Failed to extract signature
error.generate_hash = Failed to generate hash of commit
error.no_committer_account = No account linked to committer's email address
error.no_committer_account = No account linked to committers email address
error.no_gpg_keys_found = "No known key found for this signature in database"
error.not_signed_commit = "Not a signed commit"
error.failed_retrieval_gpg_keys = "Failed to retrieve any key attached to the committer's account"
error.failed_retrieval_gpg_keys = "Failed to retrieve any key attached to the committers account"
error.probable_bad_signature = "WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS."
error.probable_bad_default_signature = "WARNING! Although the default key has this ID it does not verify this commit! This commit is SUSPICIOUS."

@@ -3303,8 +3313,9 @@ train_result_cleared=The files of the task have been cleared, can not restart an
resource_use=Resource Occupancy

job_name_rule = Please enter letters, numbers, _ and - up to 64 characters and cannot end with a dash (-).
train_dataset_path_rule = The dataset location is stored in the run parameter <strong style="color:#010101">data_url</strong>, the pre-trained model is storaged in the run parameter <strong style="color:#010101">ckpt_url</strong>, and the output path is stored in the run parameter <strong style="color:#010101">train_url</strong>.
infer_dataset_path_rule = The dataset location is stored in the run parameter <strong style="color:#010101">data_url</strong>, and the output path is stored in the run parameter <strong style="color:#010101">result_url</strong>.
train_dataset_path_rule = The dataset location is stored in the run parameter <strong style="color:#010101">multi_data_url</strong>, the pre-trained model is storaged in the run parameter <strong style="color:#010101">pretrain_url</strong>, and the output path is stored in the run parameter <strong style="color:#010101">train_url</strong>.
train_dataset_path_rule_1 = The dataset location is stored in the run parameter <strong style="color:#010101">multi_data_url</strong>, the pre-trained model is storaged in the run parameter <strong style="color:#010101">pretrain_url</strong>, and please put your model into <strong style="color:#010101">/cache/output/</strong> then you can download it online.
infer_dataset_path_rule = The dataset location is stored in the run parameter <strong style="color:#010101">multi_data_url</strong>, the model is storaged in the run parameter <strong style="color:#010101">pretrain_url</strong>, and the output path is stored in the run parameter <strong style="color:#010101">result_url</strong>.
view_sample = View sample
inference_output_path_rule = The inference output path is stored in the run parameter result_url.
model_file_path_rule=The model file location is stored in the run parameter ckpt_url
@@ -3353,6 +3364,9 @@ new_train_npu_tooltips = The code is storaged in <strong style="color:#010101">%
new_infer_gpu_tooltips = The dataset is stored in <strong style="color:#010101">%s</strong>, the model file is stored in <strong style="color:#010101">%s</strong>, please store the inference output in <strong style="color:#010101">%s</strong> for subsequent downloads.
code_obs_address = Code OBS address
task_save_most_time = <p><span>*</span>The platform only retains the results of debugge, train, inference and evaluation tasks for nearly<span> 30 </span> days <span>Tasks over 30 days will not be able to download results and view logs, and cannot be debugged or trained again</span></p>
query_finetune_fail=Fail to query fine tuning job, please try again later.
finetune_max=The number of fine tuning job you created exceed the limit. please delete some first.
dataset_same_fail=The name of dataset file is used by the fine tune job, please select other dataset file.
[points]
points = points
free = Free
@@ -3414,3 +3428,13 @@ restart_failed = Restart AI task failed
system_error = System error.Please try again later
insufficient_permission = Insufficient permissions
param_error = The parameter you submitted is incorrect

[deployment]
deploy_max = The maximum deployment is %v per user
cloudbrain_error = Cannot get fineunte training task
code_copy_failed = Failed to copy the deployment codes
model_copy_failed = Failed to copy the model files
builidng_fail = Failed to build AI Model, please try again later
deletion_notice_repo = There is a deploying or running service related to this repository, please stop the service before deletion.
deletion_notice_trainjob = There is a deploying or running service related to this task, please stop the service before deletion.
stop_service_failed = Failed to stop deploy service

+ 27
- 4
options/locale/locale_zh-CN.ini View File

@@ -999,6 +999,11 @@ favorite=收藏
disassociate=取消关联
benchmark_dataset_tip=说明:先使用数据集功能上传模型,然后从数据集列表选模型。
file_deleted=文件已经被删除
select_result_file=选择结果文件
select_file=选择文件
export_file =导出
go_new_dataset=去创建数据集
export_tips = 仅支持 <span style="color:red">zip/tar.gz</span> 类型的结果文件导出,导出的文件最终可以在当前项目的数据集页签下查看。

[repo]
owner=拥有者
@@ -1060,6 +1065,7 @@ datasets.desc=数据集功能
cloudbrain_helper=使用GPU/NPU资源,开启Notebook、模型训练任务等

model_manager = 模型
model_base = 大模型基地
model_square = 模型广场
model_experience = 模型体验
model_noright=您没有操作权限。
@@ -1070,7 +1076,7 @@ notebook_select_wrong=请先选择Notebook(.ipynb)文件。
notebook_path_too_long=选择的一个或多个Notebook文件路径总长度超过255个字符,请选择路径较短的文件或调整文件路径。
notebook_branch_name_too_long=选择的一个或多个Notebook文件分支名总长度超过255个字符,请选择其他分支的文件。
notebook_file_no_right=您没有这个Notebook文件的读权限。
notebook_branch_name_not_support=分支名包含< > ' " ; \ ` = # $ % ^ ( )字符,不支持在线运行。
notebook_branch_name_not_support=分支名包含< > ' " ; \ ` = # $ % ^ ( )字符,不支持在线运行'
debug_again_fail=再次调试失败,请稍后再试。
debug_again_fail_forever=这个任务之前没有调度成功,不能再次调试。

@@ -1110,9 +1116,13 @@ images.name = 镜像Tag
images.name_placerholder = 请输入镜像Tag
image.label_tooltips = 如Python 3.7, Tensorflow 2.0, cuda 10, pytorch 1.6
images.public_tooltips = 镜像设置为公开后,可被其他用户看到。
images.name_rule = 请输入字母、数字、_和-,最长100个字符,且不能以中划线(-)结尾。
images.name_format_err=镜像Tag格式错误。
images.name_rule50 = 请输入字母、数字、_和-,最长50个字符,且以字母开头。
images.name_rule100 = 请输入字母、数字、_和-,最长100个字符,且不能以中划线(-)结尾。
images.delete_task = 删除镜像
images.task_delete_confirm = 你确认删除该镜像么?此镜像一旦删除不可恢复。
export_result_to_dataset = 导出结果至数据集
loader_result_file = 正在加载结果文件中...

cloudbrain=云脑
cloudbrain.task = 云脑任务
@@ -3323,8 +3333,9 @@ result_cleared=本任务的文件已被清理,无法再次调试,请新建
train_result_cleared=本任务的文件已被清理,无法再次训练,请新建训练任务。

job_name_rule = 请输入字母、数字、_和-,最长64个字符,且不能以中划线(-)结尾。
train_dataset_path_rule = 数据集位置存储在运行参数 <strong style="color:#010101">data_url</strong> 中,预训练模型存放在运行参数 <strong style="color:#010101">ckpt_url</strong> 中,训练输出路径存储在运行参数 <strong style="color:#010101">train_url</strong> 中。
infer_dataset_path_rule = 数据集位置存储在运行参数 <strong style="color:#010101">data_url</strong> 中,推理输出路径存储在运行参数 <strong style="color:#010101">result_url</strong> 中。
train_dataset_path_rule = 数据集位置存储在运行参数 <strong style="color:#010101">multi_data_url</strong> 中,预训练模型存放在运行参数 <strong style="color:#010101">pretrain_url</strong> 中,训练输出路径存储在运行参数 <strong style="color:#010101">train_url</strong> 中。
train_dataset_path_rule_1 = 数据集位置存储在运行参数 <strong style="color:#010101">multi_data_url</strong> 中,预训练模型存放在运行参数 <strong style="color:#010101">pretrain_url</strong> 中,训练输出请存储在 <strong style="color:#010101">/cache/output</strong> 中以供后续下载。
infer_dataset_path_rule = 数据集位置存储在运行参数 <strong style="color:#010101">multi_data_url</strong> 中,模型存放在运行参数 <strong style="color:#010101">pretrain_url</strong> 中,推理输出路径存储在运行参数 <strong style="color:#010101">result_url</strong> 中。
view_sample = 查看样例
inference_output_path_rule = 推理输出路径存储在运行参数 result_url 中。
model_file_path_rule = 模型文件位置存储在运行参数 ckpt_url 中。
@@ -3374,6 +3385,9 @@ new_train_npu_tooltips = 训练脚本存储在 <strong style="color:#010101">%s<
new_infer_gpu_tooltips = 数据集存储在 <strong style="color:#010101">%s</strong> 中,模型文件存储在 <strong style="color:#010101">%s</strong> 中,推理输出请存储在 <strong style="color:#010101">%s</strong> 中以供后续下载。
code_obs_address = 代码obs地址
task_save_most_time = <p><span>*</span> 平台仅留存近 <span>30</span> 天的调试、训练、推理、评测任务结果;<span>超过 30 天的任务将不能下载结果和查看日志,且不能再次调试或训练。</span></p>
query_finetune_fail=查询微调任务失败,请稍后重试。
finetune_max=您创建的微调任务数达到最大值,请先删除之前创建的任务再创建。
dataset_same_fail=选择的数据集文件的名称已被微调任务使用,请选择其他数据集文件。

[points]
points = 积分
@@ -3437,3 +3451,12 @@ system_error = 当前服务不可用,请稍后再试
insufficient_permission = 权限不足
param_error = 提交的参数有误

[deployment]
deploy_max = 每个用户只能同时创建 %v 个部署任务
cloudbrain_error = 无法查询到微调任务
code_copy_failed = 推理代码拷贝失败,请重新部署
model_copy_failed = 模型拷贝失败,请重新部署
builidng_fail = AI应用创建失败
deletion_notice_repo = 此项目有正在部署或正在体验的服务,请先停止服务,然后再删除。
deletion_notice_trainjob = 此任务有正在部署或正在体验的服务,请先停止服务,然后再删除。
stop_service_failed = 停止部署服务失败

BIN
public/img/ros-hmci/mbz612.png View File

Before After
Width: 3840  |  Height: 1498  |  Size: 369 KiB Width: 3840  |  Height: 1498  |  Size: 606 KiB

+ 11
- 9
routers/admin/resources.go View File

@@ -144,24 +144,24 @@ func GetResourceSpecificationList(ctx *context.Context) {
}

func GetAllResourceSpecificationList(ctx *context.Context) {
queue := ctx.QueryInt64("queue")
status := ctx.QueryInt("status")
cluster := ctx.Query("cluster")
available := ctx.QueryInt("available")
list, err := resource.GetAllDistinctResourceSpecification(models.SearchResourceSpecificationOptions{
QueueId: queue,
Status: status,
Cluster: cluster,
AvailableCode: available,
AccCardsNum: -1,
computeResource := ctx.Query("resource")
list, err := resource.GetAllResourceSpecification(models.SearchResourceSpecificationOptions{
Status: status,
Cluster: cluster,
AvailableCode: available,
AccCardsNum: -1,
ComputeResource: computeResource,
})
if err != nil {
log.Error("GetResourceSpecificationList error.%v", err)
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
return
}
ctx.JSON(http.StatusOK, response.SuccessWithData(list))
r := map[string]interface{}{"Specs": list}
ctx.JSON(http.StatusOK, response.SuccessWithData(r))
}

func GetResourceSpecificationScenes(ctx *context.Context) {
@@ -234,6 +234,7 @@ func GetResourceSceneList(ctx *context.Context) {
isExclusive := ctx.QueryInt("IsExclusive")
computeResource := ctx.Query("resource")
cardType := ctx.Query("cardType")
cluster := ctx.Query("cluster")
list, err := resource.GetResourceSceneList(models.SearchResourceSceneOptions{
ListOptions: models.ListOptions{Page: page, PageSize: 10},
JobType: jobType,
@@ -242,6 +243,7 @@ func GetResourceSceneList(ctx *context.Context) {
QueueId: queueId,
ComputeResource: computeResource,
AccCardType: cardType,
Cluster: cluster,
})
if err != nil {
log.Error("GetResourceSceneList error.%v", err)


+ 66
- 45
routers/api/v1/api.go View File

@@ -7,63 +7,66 @@
//
// This documentation describes the OpenI API.
//
// Schemes: http, https
// BasePath: /api/v1
// Version: 1.1.1
// License: MIT http://opensource.org/licenses/MIT
// Schemes: http, https
// BasePath: /api/v1
// Version: 1.1.1
// License: MIT http://opensource.org/licenses/MIT
//
// Consumes:
// - application/json
// - text/plain
// Consumes:
// - application/json
// - text/plain
//
// Produces:
// - application/json
// - text/html
// Produces:
// - application/json
// - text/html
//
// Security:
// - BasicAuth :
// - Token :
// - AccessToken :
// - AuthorizationHeaderToken :
// - SudoParam :
// - SudoHeader :
// Security:
// - BasicAuth :
// - Token :
// - AccessToken :
// - AuthorizationHeaderToken :
// - SudoParam :
// - SudoHeader :
//
// SecurityDefinitions:
// BasicAuth:
// type: basic
// Token:
// type: apiKey
// name: token
// in: query
// AccessToken:
// type: apiKey
// name: access_token
// in: query
// AuthorizationHeaderToken:
// type: apiKey
// name: Authorization
// in: header
// description: API tokens must be prepended with "token" followed by a space.
// SudoParam:
// type: apiKey
// name: sudo
// in: query
// description: Sudo API request as the user provided as the key. Admin privileges are required.
// SudoHeader:
// type: apiKey
// name: Sudo
// in: header
// description: Sudo API request as the user provided as the key. Admin privileges are required.
// SecurityDefinitions:
// BasicAuth:
// type: basic
// Token:
// type: apiKey
// name: token
// in: query
// AccessToken:
// type: apiKey
// name: access_token
// in: query
// AuthorizationHeaderToken:
// type: apiKey
// name: Authorization
// in: header
// description: API tokens must be prepended with "token" followed by a space.
// SudoParam:
// type: apiKey
// name: sudo
// in: query
// description: Sudo API request as the user provided as the key. Admin privileges are required.
// SudoHeader:
// type: apiKey
// name: Sudo
// in: header
// description: Sudo API request as the user provided as the key. Admin privileges are required.
//
// swagger:meta
package v1

import (
"code.gitea.io/gitea/entity/ai_task_entity"
"code.gitea.io/gitea/routers/ai_task"
"net/http"
"strings"

"code.gitea.io/gitea/entity/ai_task_entity"
"code.gitea.io/gitea/routers/ai_task"

"code.gitea.io/gitea/routers/api/v1/finetune"

"code.gitea.io/gitea/services/role"

"code.gitea.io/gitea/routers/api/v1/tech"
@@ -649,6 +652,10 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/no_openi", bind(api.NotOpenITechRepo{}), tech.CommitNotOpenIRepo)
m.Get("/is_admin", tech.IsAdmin)
}, reqToken())
m.Group("/finetune", func() {
m.Get("/checkRepo", finetune.CheckRepo)
m.Get("/spec", finetune.GetSpec)
}, reqToken())

m.Group("/reward_point", func() {
m.Get("/is_admin", user.IsRewardPointAdmin)
@@ -798,6 +805,7 @@ func RegisterRoutes(m *macaron.Macaron) {

m.Group("/user", func() {
m.Get("", user.GetAuthenticatedUser)
m.Get("/point_account", user.GetPointAccount)
m.Combo("/emails").Get(user.ListEmails).
Post(bind(api.CreateEmailOption{}), user.AddEmail).
Delete(bind(api.DeleteEmailOption{}), user.DeleteEmail)
@@ -1113,6 +1121,19 @@ func RegisterRoutes(m *macaron.Macaron) {
Delete(reqToken(), repo.DeleteTopic)
}, reqAdmin())
}, reqAnyRepoReader())
m.Group("/finetune", func() {

m.Get("", reqToken(), finetune.GetFinetuneJobs)
m.Post("/create", reqRepoWriter(models.UnitTypeCloudBrain), reqWeChat(), context.ReferencesGitRepo(false), bind(api.CreateFineTuneJobOption{}), finetune.CreateFineTune)
m.Group("/deploy", func() {
m.Group("/:jobid", func() {
m.Get("", finetune.GetPanguDeployStatus)
})
m.Post("/create", reqWeChat(), context.ReferencesGitRepo(false), bind(api.CreateFineTuneDeployOption{}), finetune.FineTuneDeployCreate)
m.Post("/inference", bind(api.PanguInferenceOption{}), finetune.ServiceInference)
m.Post("/update", bind(api.UpdateFineTuneDeployOption{}), finetune.ServiceUpdate)
})
}, reqToken())
m.Group("/cloudbrain", func() {
m.Get("/:id", repo.GetCloudbrainTask)
m.Get("/:id/log", repo.CloudbrainGetLog)


+ 249
- 0
routers/api/v1/finetune/finetune.go View File

@@ -0,0 +1,249 @@
package finetune

import (
"net/http"
"strconv"
"strings"

"code.gitea.io/gitea/modules/timeutil"

"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/services/cloudbrain/resource"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/cloudbrain/cloudbrainTask"
repo_service "code.gitea.io/gitea/services/repository"
)

func CheckRepo(ctx *context.APIContext) {
repo, _ := models.GetRepositoryByName(ctx.User.ID, setting.FileNoteBook.ProjectName)
var dataset *models.Dataset
var err error
if repo == nil { //创建项目和数据集
repo, err = repo_service.CreateRepository(ctx.User, ctx.User, models.CreateRepoOptions{
Name: setting.FileNoteBook.ProjectName,
Alias: "",
Description: "",
IssueLabels: "",
Gitignores: "",
License: "",
Readme: "Default",
IsPrivate: false,
AutoInit: true,
DefaultBranch: "master",
})
if err != nil {
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("repo.failed_to_create_notebook_repo", setting.FileNoteBook.ProjectName)))
return
}
dataset, err = createDataset(ctx, repo)
if err != nil {
log.Error("fail to create dataset", err)
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("dataset.create_dataset_fail")))
return
}

} else { //如果数据集不存在,创建数据集
dataset, err = models.GetDatasetByRepo(repo)
if err != nil && !models.IsErrNotExist(err) {
log.Error("fail to get dataset", err)
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("dataset.query_dataset_fail")))
return
}
if dataset == nil {
dataset, err = createDataset(ctx, repo)
if err != nil {
log.Error("fail to create dataset", err)
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("dataset.create_dataset_fail")))
return
}
}

}

ctx.JSON(http.StatusOK, models.BaseMessageApi{
Code: 0,
Message: strconv.FormatInt(dataset.ID, 10),
})

}
func GetSpec(ctx *context.APIContext) {

ctx.JSON(http.StatusOK, getFineTuneSpec(ctx.User.ID))
}

func getFineTuneSpec(uid int64) []*api.SpecificationShow {
specs, _ := resource.FindAvailableSpecs(uid, models.FindSpecsOptions{
JobType: models.JobTypeTrain,
ComputeResource: models.NPU,
Cluster: models.OpenICluster,
AiCenterCode: models.AICenterOfCloudBrainTwo,
})
var filterNoteBookSpecs = make([]*api.SpecificationShow, 0)
for _, spec := range specs {
if spec.AccCardsNum == 2 {
filterNoteBookSpecs = append(filterNoteBookSpecs, convert.ToSpecification(spec))
}
}

return filterNoteBookSpecs

}

func GetFinetuneJobs(ctx *context.APIContext) {
jobs, err := models.GetFinetuneCloudbrainsByUser(ctx.User.ID)
if err != nil {
log.Error("get finetune job error", err)
}

var fineTuneJobs = make([]*api.FinetuneJobShow, 0)

for _, job := range jobs {
fineTuneJobs = append(fineTuneJobs, convert.ToFineTuneJobShow(job))
}
ctx.JSON(http.StatusOK, map[string]interface{}{
"maxJobNum": setting.FineTune.MaxJobNum,
"fineTuneJobs": fineTuneJobs,
})

}
func CreateFineTune(ctx *context.APIContext, option api.CreateFineTuneJobOption) {
fineTuneCategory := option.SampleDatasetType

if (option.SampleDatasetType == 0 || option.SampleDatasetType > models.PanguOpenDialog) && option.Attachment == "" {
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("repo.parameter_is_wrong")))
return
}
if !isSpecValid(ctx.User.ID, option.SpecId) {
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("cloudbrain.wrong_specification")))
}

if option.Attachment != "" {
fineTuneCategory = models.PanguCustom
} else {
if len(setting.FineTune.Pangu.Dataset.Attachments) < option.SampleDatasetType || len(setting.FineTune.Pangu.Dataset.DatasetNames) < option.SampleDatasetType {
log.Error("fine tune pangu attachment config is wrong.")
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("repo.parameter_is_wrong")))
return
}
option.Attachment = setting.FineTune.Pangu.Dataset.Attachments[option.SampleDatasetType-1]
option.DatasetName = setting.FineTune.Pangu.Dataset.DatasetNames[option.SampleDatasetType-1]
}
for _, modelName := range setting.FineTune.Pangu.Model.ModelNames {
if modelName == option.DatasetName {
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("cloudbrain.dataset_same_fail")))
return
}
}
//supply models dataset
option.Attachment = option.Attachment + ";" + strings.Join(setting.FineTune.Pangu.Model.ModelAttachments, ";")
option.DatasetName = option.DatasetName + ";" + strings.Join(setting.FineTune.Pangu.Model.ModelNames, ";")

fineTuneRepo, err := models.GetRepositoryByOwnerAndName(setting.FineTune.Pangu.Basic.OwnerName, setting.FineTune.Pangu.Basic.RepoName)
if err != nil {
log.Error("Can not get pangu repo", err)
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("repo.parameter_is_wrong")))
return
}

jobCount, err := models.GetFinetuneCloudbrainsCountByUser(ctx.User.ID)
if err != nil {
log.Error("Can not get finetune job count", err)
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("cloudbrain.query_finetune_fail")))
return
}
if jobCount >= int64(setting.FineTune.MaxJobNum) {
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("cloudbrain.finetune_max")))
return
}

trainJobOption := api.CreateTrainJobOption{
Type: cloudbrainTask.TaskTypeModelArts,
DisplayJobName: option.DisplayJobName,
ImageID: strconv.FormatInt(setting.FineTune.Pangu.Basic.ImageId, 10),
Image: setting.FineTune.Pangu.Basic.Image,
Attachment: option.Attachment,
DatasetName: option.DatasetName,
Description: option.Description,
BootFile: setting.FineTune.Pangu.Basic.BootFile,
BranchName: setting.FineTune.Pangu.Basic.BranchName,
Params: option.Params,
WorkServerNumber: 1,
SpecId: option.SpecId,
FineTune: true,
FineTuneModelType: models.PanguModelFineTune,
FineTuneCategory: fineTuneCategory,
CodeRepo: fineTuneRepo,
}

cloudbrainTask.ModelArtsTrainJobNpuCreate(ctx.Context, trainJobOption)

}

func isSpecValid(uid int64, specId int64) bool {
availableSpecs := getFineTuneSpec(uid)
for _, availableSpec := range availableSpecs {
if availableSpec.ID == specId {
return true
}
}
return false
}

func createDataset(ctx *context.APIContext, repoNew *models.Repository) (*models.Dataset, error) {
dataset := &models.Dataset{}

dataset.RepoID = repoNew.ID
dataset.UserID = ctx.User.ID
dataset.Category = "natural_language_processing"
dataset.Task = "machine_translation"
dataset.Title = repoNew.Name

dataset.Description = "Fine tuning"
dataset.DownloadTimes = 0
if repoNew.IsPrivate {
dataset.Status = 0
} else {
dataset.Status = 1
}
err := models.CreateDataset(dataset)
return dataset, err

}

func FineTuneDeployCreate(ctx *context.APIContext, option api.CreateFineTuneDeployOption) {
// get running service count
deployments, _ := models.GetRunningServiceByUser(ctx.User.ID)
log.Info("调试,%s, %v", ctx.User.ID, len(deployments))
if len(deployments) >= setting.FineTune.Pangu.Deploy.MaxDeployPerUser {
log.Error("盘古微调部署: 每个用户最多只能同时部署%v个模型", setting.FineTune.Pangu.Deploy.MaxDeployPerUser)
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("deployment.deploy_max", setting.FineTune.Pangu.Deploy.MaxDeployPerUser)))
return
}
cloudbrainJob, _ := models.GetCloudbrainByJobID(option.JobID)
models.CreateModelartsDeploy(&models.ModelartsDeploy{
JobID: option.JobID,
UserID: cloudbrainJob.UserID,
JobName: cloudbrainJob.JobName,
Status: "BUILDING",
DisplayJobName: cloudbrainJob.DisplayJobName,
CreateUnix: timeutil.TimeStampNow(),
UpdateUnix: timeutil.TimeStampNow(),
Finetune: true,
FinetuneModelType: cloudbrainJob.FineTuneModelType,
FinetuneCategory: cloudbrainJob.FineTuneCategory,
})

err := CreateAIModel(ctx, option.JobID)
if err != nil {
log.Error("盘古微调部署: 微调任务 %s 创建AI应用失败 %v", option.JobID, err.Error())
}
ctx.JSON(http.StatusOK, models.BaseMessageApi{
Code: 0,
Message: option.JobID,
})
}

+ 277
- 0
routers/api/v1/finetune/panguervice.go View File

@@ -0,0 +1,277 @@
package finetune

import (
"net/http"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"

// for deployment
"code.gitea.io/gitea/modules/modelarts"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/timeutil"
)

const (
PanguModelType = "MindSpore"
PanguModelVersion = "1.0.0"
PanguDeployType = "real-time"
PanguDeployInstanceCnt = 1
//PanguDeploySpec = "custom"
PanguDeployScheduleType = "stop"
)

func CreateAIModel(ctx *context.APIContext, jobID string) error {
deploy, _ := models.GetModelartsDeployByJobID(jobID)

jobName := deploy.JobName
deployPath := modelarts.JobPath[1:] + jobName + "/model/"
panguBucket := setting.FineTune.Pangu.Deploy.CodeBucket
panguCodePath := setting.FineTune.Pangu.Deploy.CodeObsPath
if _, err := storage.ObsCopyAllFile(panguBucket, panguCodePath, setting.Bucket, deployPath); err != nil {
log.Error("panguService: jobID %s Modelart ModelartsDeploy: Failed to copy %s to /model (%v)", jobID, panguCodePath, err)
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("deployment.code_copy_failed")))
return err
}
//log.Info("panguService: 拷贝推理代码文件成功, 源路径: %s, 目标路径: (%s)", panguBucket+"/"+panguCodePath, setting.Bucket+"/"+deployPath)

trainModel := modelarts.JobPath[1:] + jobName + modelarts.OutputPath + "V0001/" + setting.FineTune.Pangu.Deploy.ModelName
deployModel := deployPath + setting.FineTune.Pangu.Deploy.ModelName
if err := storage.ObsCopyFile(setting.Bucket, trainModel, setting.Bucket, deployModel); err != nil {
log.Error("panguService: jobID %s Modelart ModelartsDeploy: Failed to copy %s to /model (%v)", jobID, trainModel, err)
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("deployment.model_copy_failed")))
return err
}
//log.Info("panguService: 拷贝模型成功, 源路径: %s, 目标路径: %s", setting.Bucket+"/"+trainModel, setting.Bucket+"/"+deployPath)
deploy.DeployUrl = setting.Bucket + "/" + deployPath
deploy.UpdateUnix = timeutil.TimeStampNow()
models.UpdateDeploy(deploy)

deployModelReq := &modelarts.GenerateDeployModelReq{
JobID: jobID,
ModelName: jobName + "_model",
ModelVersion: PanguModelVersion,
ModelType: PanguModelType,
SourceLocation: setting.Endpoint[:8] + setting.Bucket + "." + setting.Endpoint[8:] + "/" + deployPath,
UserID: deploy.UserID,
Runtime: setting.FineTune.Pangu.Deploy.Runtime,
InstallType: []string{PanguDeployType},
}

modelID, err := modelarts.GenerateDeployModel(ctx.Context, deployModelReq)
if err != nil {
log.Error("panguService: 微调任务 [%s] AI应用 API调用失败:%v", jobID, err.Error())
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("deployment.builidng_fail")))
return err
}
log.Info("panguService: 微调任务 [%s], AI应用API调用成功, 模型ID [%s]", jobID, modelID)
return nil
}

func PanguServiceCreateQueue() {
runningDeploys, _ := models.GetAllRunningService()
var cntQueue int
if runningDeploys == nil {
cntQueue = setting.FineTune.Pangu.Deploy.MaxDeployNum
//log.Info("panguService: 部署队列 当前无部署任务,可用运行资源 %v 卡。", cntQueue)
} else {
cntQueue = setting.FineTune.Pangu.Deploy.MaxDeployNum - len(runningDeploys)
log.Info("panguService: 部署队列 当前正在运行 %v 部署任务,可用运行资源 %v 卡。", len(runningDeploys), cntQueue)
if cntQueue == 0 {
//log.Info("panguService: 资源已满,停止当前定时任务。")
return
}
}

deployQueues, _ := models.GetModelartsDeployQueue(cntQueue)
if deployQueues == nil {
//log.Error("panguService: 部署队列 空,停止当前定时任务。")
return
}

for _, deployQueue := range deployQueues {
if deployQueue.ServiceID != "" {
//服务重启
err := modelarts.UpdateDeployService(deployQueue.ServiceID, models.UpdateDeployServiceParams{
Status: "running",
})
if err != nil {
log.Error("panguService: 微调任务 [%s],部署服务重启失败, 服务ID %s", deployQueue.JobID, deployQueue.ServiceID)
models.DeleteModelartsDeployQueueByJobID(deployQueue.JobID)
continue
}
log.Error("panguService: 微调任务 [%s],部署服务重启成功", deployQueue.JobID)
} else {
//初次部署服务
serviceReq := &modelarts.GenerateDeployServiceReq{
JobID: deployQueue.JobID,
InferType: PanguDeployType,
ServiceName: deployQueue.ModelName + "-service",
Spec: setting.FineTune.Pangu.Deploy.Specification,
Duration: setting.FineTune.Pangu.Deploy.Duration + setting.FineTune.Pangu.Deploy.WarmupDuration,
TimeUnit: setting.FineTune.Pangu.Deploy.TimeUnit,
ScheduleType: PanguDeployScheduleType,
ModelID: deployQueue.ModelID,
InstanceCount: PanguDeployInstanceCnt,
}
serviceID, err := modelarts.GenerateDeployService(serviceReq)
if err != nil {
log.Error("panguService: 微调任务 [%s],部署服务创建失败, 服务ID %s", deployQueue.JobID, serviceID)
models.DeleteModelartsDeployQueueByJobID(deployQueue.JobID)
continue
}
log.Info("panguService: 微调任务 [%s] 部署服务创建成功, serviceID %s", deployQueue.JobID, serviceID)
}
models.DeleteModelartsDeployQueueByJobID(deployQueue.JobID)
}
}

func SyncPanguDeployStatus() {
deployments, _ := models.GetAllModelartsDeploys()
for _, deploy := range deployments {
var statusNew string
if deployQueue, _ := models.GetModelartsDeployQueueByJobID(deploy.JobID); deployQueue != nil || deploy.Status == "FAILED" {
log.Info("panguServic: 微调任务 [%s] 不更新状态, status %s", deploy.JobID, deploy.Status)
continue
} else if deploy.ServiceID == "" {
// 查模型
deployNew, err := modelarts.GetDeployModel(deploy.ModelID)
if err != nil {
log.Error("panguService: 微调任务 [%s] Get DeployModel API failed:%v", deploy.DisplayJobName, err.Error())
return
}
statusNew = models.DeployStatusConvert(deployNew.ModelStatus)
statusOld := deploy.Status
deploy.Status = statusNew
deploy.ModelStatus = deployNew.ModelStatus
log.Info("panguService: status update 微调任务 [%s], OLD %s, NEW %s, model api %s", deploy.JobID, statusOld, statusNew, deployNew.ModelStatus)
if statusOld == "BUILDING" && statusNew == "WAITING" {
models.CreateModelartsDeployQueue(&models.ModelartsDeployQueue{
JobID: deploy.JobID,
ModelID: deploy.ModelID,
ModelName: deploy.ModelName,
DisplayJobName: deploy.DisplayJobName,
CreateUnix: timeutil.TimeStampNow(),
})
}
} else {
// 查服务
deployNew, err := modelarts.GetDeployService(deploy.ServiceID)
if err != nil {
log.Error("panguService: 微调任务 [%s] Get DeployService API failed:%v", deploy.DisplayJobName, err.Error())
return
}
statusNew = models.DeployStatusConvert(deployNew.Status)
deploy.ServiceStatus = deployNew.Status
// 部署成功后,等待30分钟再允许用户打开推理界面
if statusNew == "SUCCEEDED" {
currentTime := timeutil.TimeStampNow()
//log.Info("panguService:deploy.CompleteUnix.IsZero() %v, deploy.CompleteUnix == timeutil.TimeStamp(0) %v", deploy.CompleteUnix.IsZero(), deploy.CompleteUnix == timeutil.TimeStamp(0))
if deploy.CompleteUnix == timeutil.TimeStamp(0) {
warmupTime := int64(setting.FineTune.Pangu.Deploy.WarmupDuration * 60)
deploy.CompleteUnix = currentTime.Add(warmupTime)
deploy.UpdateUnix = currentTime
models.UpdateDeploy(deploy)
log.Info("panguService:%s 部署成功,预热开始,预计完成时间:%v", deploy.DisplayJobName, deploy.CompleteUnix)
continue
} else if deploy.CompleteUnix >= currentTime {
log.Info("panguService:%s 预热中,预计完成时间:%v", deploy.DisplayJobName, deploy.CompleteUnix)
continue
}
}
statusOld := deploy.Status
deploy.Status = statusNew
deploy.InferAddr = deployNew.InferAddr
log.Info("panguService: status update 微调任务 [%s], OLD %s, NEW %s, service api %s", deploy.JobID, statusOld, statusNew, deployNew.Status)
if statusNew == "SUCCEEDED" && statusOld == "DEPLOYING" {
notification.NotifyChangeFinetuneStatus(deploy)
log.Info("panguService:%s 部署服务预热完成,发送微信通知", deploy.DisplayJobName)
}
}
deploy.UpdateUnix = timeutil.TimeStampNow()
models.UpdateDeploy(deploy)
}
}

func GetPanguDeployStatus(ctx *context.APIContext) {
var jobID = ctx.Params(":jobid")
status, _ := models.GetModelartsDeployStatusByJobID(jobID)
ctx.JSON(http.StatusOK, map[string]interface{}{
"fineTuneDeployStatus": status,
})
}

func ServiceInference(ctx *context.APIContext, option api.PanguInferenceOption) {
deploy, err := models.GetModelartsDeployByJobID(option.JobID)
if err != nil {
log.Error("panguService: %s Get ModelartsDeploy from DB failed:%v", deploy.DisplayJobName, err.Error())
return
}

//serviceID := deploy.ServiceID
inferAddr := deploy.InferAddr
inferResults, err := modelarts.SendInferenceDeploy(inferAddr, option.Text)
if err != nil {
log.Error("panguService: %s SendInferenceDeploy API failed:%v", deploy.DisplayJobName, err.Error())
ctx.JSON(http.StatusOK, map[string]interface{}{
"code": 1,
"text": err.Error(),
})
return
}
log.Info("panguService: %s 推理结果返回成功, %s", deploy.DisplayJobName, inferResults.GenerateResult)
ctx.JSON(http.StatusOK, map[string]interface{}{
"code": 0,
"text": inferResults.GenerateResult,
})
}

func ServiceUpdate(ctx *context.APIContext, option api.UpdateFineTuneDeployOption) {

jobID := option.JobID
deploy, err := models.GetModelartsDeployByJobID(jobID)
if err != nil {
log.Error("panguService: Get ModelartsDeploy from DB failed:%v", err.Error())
return
}

if option.Status == "running" {
// get running service count
deployments, _ := models.GetRunningServiceByUser(ctx.User.ID)
if len(deployments) >= setting.FineTune.Pangu.Deploy.MaxDeployPerUser {
log.Error("panguService: 每个用户最多只能同时部署%v个模型", ctx.User.ID, len(deployments))
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("deployment.deploy_max", setting.FineTune.Pangu.Deploy.MaxDeployPerUser)))
return
}
//log.Info("panguService: 重启服务开始")
deploy.Status = "WAITING"
deploy.CompleteUnix = timeutil.TimeStamp(0)
models.CreateModelartsDeployQueue(&models.ModelartsDeployQueue{
JobID: jobID,
ModelID: deploy.ModelID,
ModelName: deploy.ModelName,
ServiceID: deploy.ServiceID,
CreateUnix: timeutil.TimeStampNow(),
})
} else {
log.Info("panguService: 停止服务")
deploy.Status = "STOP"
err := modelarts.UpdateDeployService(deploy.ServiceID, models.UpdateDeployServiceParams{
Status: option.Status,
})
if err != nil {
log.Error("panguService: %s Update DeployService API failed:%v", deploy.DisplayJobName, err.Error())
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("deployment.stop_service_failed")))
}
}
deploy.UpdateUnix = timeutil.TimeStampNow()
models.UpdateDeploy(deploy)
ctx.JSON(http.StatusOK, models.BaseMessageApi{
Code: 0,
Message: option.JobID,
})
}

+ 8
- 2
routers/api/v1/repo/modelarts.go View File

@@ -6,7 +6,6 @@
package repo

import (
"code.gitea.io/gitea/services/ai_task_service/schedule"
"encoding/json"
"net/http"
"path"
@@ -15,6 +14,8 @@ import (
"strings"
"time"

"code.gitea.io/gitea/services/ai_task_service/schedule"

"code.gitea.io/gitea/routers/response"

"code.gitea.io/gitea/modules/cloudbrain"
@@ -377,7 +378,12 @@ func DelTrainJobVersion(ctx *context.APIContext) {
_, err = modelarts.DelTrainJobVersion(jobID, strconv.FormatInt(task.VersionID, 10))
if err != nil {
log.Error("DelTrainJobVersion(%s) failed:%v", task.JobName, err.Error())
ctx.NotFound(err)
if err.Error() == "1" {
ctx.JSON(http.StatusOK, map[string]interface{}{
"Message": ctx.Tr("deployment.deletion_notice_trainjob"),
"StatusOK": 1,
})
}
return
}



+ 22
- 0
routers/api/v1/user/cloudbrain.go View File

@@ -0,0 +1,22 @@
package user

import (
"net/http"

"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/reward/point/account"
)

func GetPointAccount(ctx *context.APIContext) {
a, err := account.GetAccount(ctx.User.ID)
if err != nil {
ctx.ServerError("GetPointAccount", err)
return
}
ctx.JSON(http.StatusOK, map[string]interface{}{
"pointAccount": convert.ToPointAccount(a),
"cloudBrainPaySwitch": setting.CloudBrainPaySwitch,
})
}

+ 31
- 0
routers/modelapp/pangu.go View File

@@ -0,0 +1,31 @@
package modelapp

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/services/cloudbrain/cloudbrainTask"
)

var tplModelBase base.TplName = "model/base/index"
var tplPanguFinetuneHome base.TplName = "model/base/panguIndex"
var tplPanguFinetuneCreate base.TplName = "model/base/panguCreate"
var tplPanguInference base.TplName = "model/base/panguInference"

func ModelBaseUI(ctx *context.Context) {
ctx.HTML(200, tplModelBase)
}

func PanguFinetuneUI(ctx *context.Context) {
ctx.HTML(200, tplPanguFinetuneHome)
}

func PanguFinetuneCreateUI(ctx *context.Context) {
NotStopTaskCount, _ := cloudbrainTask.GetNotFinalStatusTaskCount(ctx.User.ID, string(models.JobTypeTrain))
ctx.Data["NotStopTaskCount"] = NotStopTaskCount
ctx.HTML(200, tplPanguFinetuneCreate)
}

func PanguInferenceUI(ctx *context.Context) {
ctx.HTML(200, tplPanguInference)
}

+ 11
- 8
routers/repo/ai_model_manage.go View File

@@ -2,7 +2,6 @@ package repo

import (
"archive/zip"
"code.gitea.io/gitea/services/cloudbrain/resource"
"encoding/json"
"errors"
"fmt"
@@ -12,6 +11,8 @@ import (
"regexp"
"strings"

"code.gitea.io/gitea/services/cloudbrain/resource"

"code.gitea.io/gitea/services/cloudbrain/modelmanage"

"code.gitea.io/gitea/services/repository"
@@ -151,8 +152,10 @@ func saveModelByParameters(jobId string, versionName string, name string, versio
}

func asyncToCopyModel(aiTask *models.Cloudbrain, id string, modelSelectedFile string) {
destKeyNamePrefix := Model_prefix + models.AttachmentRelativePath(id) + "/"
if aiTask.ComputeResource == models.NPUResource {
modelPath, modelSize, err := downloadModelFromCloudBrainTwo(id, aiTask.JobName, "", aiTask.TrainUrl, modelSelectedFile)
//destKeyNamePrefix := Model_prefix + models.AttachmentRelativePath(modelUUID) + "/"
modelPath, modelSize, err := downloadModelFromCloudBrainTwo(aiTask.JobName, "", aiTask.TrainUrl, modelSelectedFile, destKeyNamePrefix)
if err != nil {
updateStatus(id, 0, STATUS_ERROR, modelPath, err.Error())
log.Info("download model from CloudBrainTwo faild." + err.Error())
@@ -160,9 +163,9 @@ func asyncToCopyModel(aiTask *models.Cloudbrain, id string, modelSelectedFile st
updateStatus(id, modelSize, STATUS_FINISHED, modelPath, "")
insertModelFile(id)
}
} else if aiTask.ComputeResource == models.GPUResource {
} else if aiTask.ComputeResource == models.GPUResource || aiTask.ComputeResource == models.GCUResource {

modelPath, modelSize, err := downloadModelFromCloudBrainOne(id, aiTask.JobName, "", aiTask.TrainUrl, modelSelectedFile)
modelPath, modelSize, err := downloadModelFromCloudBrainOne(aiTask.JobName, "", modelSelectedFile, destKeyNamePrefix)
if err != nil {
updateStatus(id, 0, STATUS_ERROR, modelPath, err.Error())
log.Info("download model from CloudBrainOne faild." + err.Error())
@@ -438,7 +441,7 @@ func SaveModel(ctx *context.Context) {
log.Info("save model end.")
}

func downloadModelFromCloudBrainTwo(modelUUID string, jobName string, parentDir string, trainUrl string, modelSelectedFile string) (string, int64, error) {
func downloadModelFromCloudBrainTwo(jobName string, parentDir string, trainUrl string, modelSelectedFile string, destKeyNamePrefix string) (string, int64, error) {
objectkey := strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir), "/")
if trainUrl != "" {
objectkey = strings.Trim(trainUrl[len(setting.Bucket)+1:], "/")
@@ -465,15 +468,15 @@ func downloadModelFromCloudBrainTwo(modelUUID string, jobName string, parentDir
return "", 0, errors.New("Cannot create model, as model is empty.")
}

destKeyNamePrefix := Model_prefix + models.AttachmentRelativePath(modelUUID) + "/"
//destKeyNamePrefix := Model_prefix + models.AttachmentRelativePath(modelUUID) + "/"
size, err := storage.ObsCopyManyFile(setting.Bucket, prefix, setting.Bucket, destKeyNamePrefix, filterFiles)

dataActualPath := setting.Bucket + "/" + destKeyNamePrefix
return dataActualPath, size, nil
}

func downloadModelFromCloudBrainOne(modelUUID string, jobName string, parentDir string, trainUrl string, modelSelectedFile string) (string, int64, error) {
destKeyNamePrefix := Model_prefix + models.AttachmentRelativePath(modelUUID) + "/"
func downloadModelFromCloudBrainOne(jobName string, parentDir string, modelSelectedFile string, destKeyNamePrefix string) (string, int64, error) {
//destKeyNamePrefix := Model_prefix + models.AttachmentRelativePath(modelUUID) + "/"

modelSrcPrefix := setting.CBCodePathPrefix + jobName + "/model/"
//destKeyNamePrefix := Model_prefix + models.AttachmentRelativePath(modelUUID) + "/"


+ 22
- 6
routers/repo/ai_model_square.go View File

@@ -252,13 +252,15 @@ func ModelEvolutionMapData(ctx *context.Context) {
"code": "-1",
}
if err == nil {

removeIpInfo(model)
repo, err := models.GetRepositoryByID(model.RepoId)
model.RepoName = repo.Name
model.RepoOwnerName = repo.OwnerName
model.RepoDisplayName = repo.DisplayName()
if err == nil {
model.RepoName = repo.Name
model.RepoOwnerName = repo.OwnerName
model.RepoDisplayName = repo.DisplayName()
setModelUser(model)
setModelDataSet(model)
currentNode := &ModelMap{
Type: 1,
IsCurrent: true,
@@ -287,6 +289,16 @@ func ModelEvolutionMapData(ctx *context.Context) {
}
}

func setModelDataSet(model *models.AiModelManage) {
if model.TrainTaskInfo != "" {
var task models.Cloudbrain
err1 := json.Unmarshal([]byte(model.TrainTaskInfo), &task)
if err1 == nil {
model.DatasetInfo = GetCloudBrainDataSetInfo(task.Uuid, task.DatasetName, false)
}
}
}

func findParent(model *models.AiModelManage) *ModelMap {
if model.TrainTaskInfo != "" {
var task models.Cloudbrain
@@ -300,6 +312,7 @@ func findParent(model *models.AiModelManage) *ModelMap {
parentModel, err := models.QueryModelById(task.ModelId)
setModelRepo(parentModel)
setModelUser(parentModel)
setModelDataSet(parentModel)
if err == nil {
re := &ModelMap{
Type: 1,
@@ -314,6 +327,7 @@ func findParent(model *models.AiModelManage) *ModelMap {
for _, parentModel := range modelList {
setModelUser(parentModel)
setModelRepo(parentModel)
setModelDataSet(parentModel)
if parentModel.Version == task.ModelVersion {
re := &ModelMap{
Type: 1,
@@ -362,6 +376,7 @@ func findChild(currentNode *ModelMap) {
log.Info("task.ModelId=%v,currentModel.ID=%v", task.ModelId, currentModel.ID)
if task.ModelId != "" && task.ModelId == currentModel.ID {
setModelUser(childModel)
setModelDataSet(childModel)
modelMap := &ModelMap{
Type: 1,
Model: childModel,
@@ -372,6 +387,7 @@ func findChild(currentNode *ModelMap) {
log.Info("task.ModelVersion=%v,currentModel.Version=%v", task.ModelVersion, currentModel.Version)
if task.ModelName == currentModel.Name && task.ModelVersion == currentModel.Version {
setModelUser(childModel)
setModelDataSet(childModel)
modelMap := &ModelMap{
Type: 1,
Model: childModel,
@@ -469,14 +485,14 @@ func QueryModelReadMe(ctx *context.Context) {
}
}
}
metas:=map[string]string{"include_toc":"true"}
metas := map[string]string{"include_toc": "true"}
if find {
re["isExistMDFile"] = "true"
re["fileName"] = README_FILE_NAME
strc := string(content)
re["content"] = strc

re["htmlcontent"] = string(markdown.RenderRaw([]byte(strc), "", false,metas))
re["htmlcontent"] = string(markdown.RenderRaw([]byte(strc), "", false, metas))
} else {
re["isExistMDFile"] = "false"
re["fileName"] = README_FILE_NAME
@@ -484,7 +500,7 @@ func QueryModelReadMe(ctx *context.Context) {
result, err := repository.RecommendContentFromPromote(url)
if err == nil {
re["content"] = result
re["htmlcontent"] = string(markdown.RenderRaw([]byte(result), "", false,metas))
re["htmlcontent"] = string(markdown.RenderRaw([]byte(result), "", false, metas))
}
}
re["code"] = "0"


+ 28
- 16
routers/repo/attachment.go View File

@@ -190,8 +190,9 @@ func DeleteAttachment(ctx *context.Context) {

_, err = models.DeleteFileChunkById(attach.UUID)
if err != nil {
ctx.Error(500, fmt.Sprintf("DeleteFileChunkById: %v", err))
return
log.Info("delete from file chunk failed.")
//ctx.Error(500, fmt.Sprintf("DeleteFileChunkById: %v", err))
//return
}
ctx.JSON(200, map[string]string{
"uuid": attach.UUID,
@@ -878,27 +879,39 @@ func CompleteMultipart(ctx *context.Context) {
ctx.Error(500, fmt.Sprintf("UpdateFileChunk: %v", err))
return
}
dataset, _ := models.GetDatasetByID(ctx.QueryInt64("dataset_id"))

err = finishedUploadAttachment(ctx.QueryInt64("dataset_id"), ctx.QueryInt64("size"), uuid, fileName, ctx.Query("description"), typeCloudBrain, ctx.User)
if err == nil {
ctx.JSON(200, map[string]string{
"result_code": "0",
})
} else {
ctx.JSON(200, map[string]string{
"result_code": "-1",
"msg": err.Error(),
})
}

}

func finishedUploadAttachment(datasetId, size int64, uuid, fileName, description string, typeCloudBrain int, User *models.User) error {
dataset, _ := models.GetDatasetByID(datasetId)
log.Warn("insert attachment to datasetId:" + strconv.FormatInt(dataset.ID, 10))
attachment, err := models.InsertAttachment(&models.Attachment{
UUID: uuid,
UploaderID: ctx.User.ID,
UploaderID: User.ID,
IsPrivate: dataset.IsPrivate(),
Name: fileName,
Size: ctx.QueryInt64("size"),
DatasetID: ctx.QueryInt64("dataset_id"),
Description: ctx.Query("description"),
Size: size,
DatasetID: datasetId,
Description: description,
Type: typeCloudBrain,
})

if err != nil {
ctx.Error(500, fmt.Sprintf("InsertAttachment: %v", err))
return
return errors.New("InsertAttachment: " + err.Error())
}
attachment.UpdateDatasetUpdateUnix()
go repo_service.IncreaseRepoDatasetNum(dataset.ID)
repository, _ := models.GetRepositoryByID(dataset.RepoID)
notification.NotifyOtherTask(ctx.User, repository, fmt.Sprint(repository.IsPrivate, attachment.IsPrivate), attachment.Name, models.ActionUploadAttachment)
if attachment.DatasetID != 0 {
if isCanDecompress(attachment.Name) {
if typeCloudBrain == models.TypeCloudBrainOne {
@@ -930,10 +943,9 @@ func CompleteMultipart(ctx *context.Context) {
labelmsg.SendAddAttachToLabelSys(string(attachjson))
}
}

ctx.JSON(200, map[string]string{
"result_code": "0",
})
repository, _ := models.GetRepositoryByID(dataset.RepoID)
notification.NotifyOtherTask(User, repository, fmt.Sprint(repository.IsPrivate, attachment.IsPrivate), attachment.Name, models.ActionUploadAttachment)
return nil
}

func HandleUnDecompressAttachment() {


+ 12
- 8
routers/repo/cloudbrain.go View File

@@ -966,7 +966,7 @@ func cloudBrainShow(ctx *context.Context, tpName base.TplName, jobType models.Jo
ctx.Data["code_path"] = cloudbrain.CodeMountPath
ctx.Data["dataset_path"] = cloudbrain.DataSetMountPath
ctx.Data["model_path"] = cloudbrain.ModelMountPath
ctx.Data["canDownload"] = cloudbrain.CanModifyJob(ctx, task)
ctx.Data["canDownload"] = cloudbrain.CanDownloadJob(ctx, task)
ctx.Data["branchName"] = task.BranchName
ctx.HTML(200, tpName)
}
@@ -1222,7 +1222,7 @@ func CloudBrainAdminCommitImage(ctx *context.Context, form auth.CommitAdminImage
func CloudBrainCommitImage(ctx *context.Context, form auth.CommitImageCloudBrainForm) {

if !NamePattern.MatchString(form.Tag) {
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("dataset.title_format_err")))
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("repo.images.name_format_err")))
return
}

@@ -1425,11 +1425,6 @@ func StopJobs(cloudBrains []*models.Cloudbrain) {

func DeleteJobs(cloudBrains []*models.Cloudbrain) {
for _, taskInfo := range cloudBrains {
err := models.DeleteJob(taskInfo)
if err != nil {
log.Warn("Failed to DeleteJob:", err)
return
}
if taskInfo.Type == models.TypeCloudBrainOne {
cloudbrain.DelCloudBrainJob(taskInfo.JobName)
cloudbrainTask.DeleteCloudbrainJobStorage(taskInfo.JobName, models.TypeCloudBrainOne)
@@ -1437,7 +1432,11 @@ func DeleteJobs(cloudBrains []*models.Cloudbrain) {
if taskInfo.Type == models.TypeCloudBrainTwo {
if taskInfo.JobType == string(models.JobTypeTrain) || taskInfo.JobType == string(models.JobTypeInference) {

modelarts.DelTrainJob(taskInfo.JobID)
_, err := modelarts.DelTrainJob(taskInfo.JobID)
if err != nil {
log.Error("Failed to delete cloudbrain job on modelarts.")
continue
}
DeleteJobStorage(taskInfo.JobName)
}
if taskInfo.JobType == string(models.JobTypeDebug) {
@@ -1450,6 +1449,11 @@ func DeleteJobs(cloudBrains []*models.Cloudbrain) {
cloudbrainTask.DeleteCloudbrainJobStorage(taskInfo.JobName, models.TypeCloudBrainOne)
}
}
err := models.DeleteJob(taskInfo)
if err != nil {
log.Warn("Failed to DeleteJob:", err)
return
}
}
}



+ 287
- 0
routers/repo/dataset.go View File

@@ -4,12 +4,16 @@ import (
"encoding/json"
"fmt"
"net/http"
"path"
"sort"
"strconv"
"strings"
"time"
"unicode/utf8"

"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/services/repository"
uuid "github.com/satori/go.uuid"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
@@ -17,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
)

const (
@@ -541,3 +546,285 @@ func GetDatasetStatus(ctx *context.Context) {
"AttachmentStatus": fmt.Sprint(attachment.DecompressState),
})
}

func GetDataSetSelectItemByJobId(ctx *context.Context) {
JobID := ctx.Query("jobId")
versionName := ctx.Query("versionName")
re := map[string]interface{}{
"code": "-1",
}
task, err := models.GetCloudbrainByJobIDAndVersionName(JobID, versionName)
if err != nil {
task, err = models.GetRepoCloudBrainByJobID(ctx.Repo.Repository.ID, JobID)
if err != nil {
log.Info("query task error." + err.Error())
re["msg"] = "Query cloudbrain task error." + err.Error()
ctx.JSON(200, re)
return
}
}
var taskType int
if task.ComputeResource == models.NPUResource {
taskType = models.TypeCloudBrainTwo
} else if task.ComputeResource == models.GPUResource || task.ComputeResource == models.GCUResource {
taskType = models.TypeCloudBrainOne
}
result, err := getModelFromObjectSave(task.JobName, taskType, task.VersionName)
if err != nil {
re["msg"] = "Query model file error, " + err.Error()
ctx.JSON(200, re)
return
}
reFile := make([]storage.FileInfo, 0)
for _, file := range result {
if strings.HasSuffix(strings.ToLower(file.FileName), ".zip") || strings.HasSuffix(strings.ToLower(file.FileName), ".tar.gz") {
reFile = append(reFile, file)
}
}
re["code"] = 0
re["files"] = reFile
ctx.JSON(200, re)
}

func GetExportDataSetByMsgId(ctx *context.Context) {
progressId := ctx.Query("progressId")
progress, err := redis_client.Get(progressId)
if err == nil {
ctx.JSON(200, progress)
} else {
ctx.JSON(200, -1)
}
}

func GetCurrentDataSet(ctx *context.Context) {
re := map[string]interface{}{
"code": "-1",
}
dataset, err := models.GetDatasetByRepo(ctx.Repo.Repository)
if err == nil {
re["code"] = 0
re["dataset"] = dataset
ctx.JSON(200, re)
} else {
ctx.JSON(200, re)
}
}

func ExportModelToExistDataSet(ctx *context.Context) {
modelSelectedFile := ctx.Query("modelSelectedFile")
log.Info("modelSelectedFile=" + modelSelectedFile)
datasetId := ctx.QueryInt64("datasetId")
log.Info("datasetId=" + fmt.Sprint(datasetId))

re := map[string]string{
"code": "-1",
}
description := ctx.Query("description")
jobId := ctx.Query("jobId")
storeType := ctx.QueryInt("type")
versionName := ctx.Query("versionName")
dataset, err := models.GetDatasetByID(datasetId)
if err != nil || dataset == nil {
log.Info("Not found dataset.")
re["msg"] = "Not found dataset."
ctx.JSON(200, re)
return
}
aiTask, err := models.GetCloudbrainByJobIDAndVersionName(jobId, versionName)
if err != nil {
aiTask, err = models.GetRepoCloudBrainByJobID(ctx.Repo.Repository.ID, jobId)
if err != nil {
log.Info("query task error." + err.Error())
re["msg"] = "Query cloudbrain task error." + err.Error()
ctx.JSON(200, re)
return
}
}
msgKey := fmt.Sprint(datasetId) + "_" + jobId + "_" + versionName
msgMap := make(map[string]int, 0)
msgMap["##type##"] = storeType
filterFiles := strings.Split(modelSelectedFile, ";")
for _, shortFile := range filterFiles {
msgMap[shortFile] = 0
}
setProgress(msgKey, msgMap)
go asyncToExportDataset(dataset, storeType, modelSelectedFile, aiTask, ctx.User, msgKey, msgMap, versionName, description)
ctx.JSON(200, map[string]string{
"code": "0",
"progressId": msgKey,
})
}

func setProgress(msgKey string, msgMap map[string]int) {
msgMapJson, _ := json.Marshal(msgMap)
redisValue := string(msgMapJson)
log.Info("set redis key=" + msgKey + " value=" + redisValue)
re, err := redis_client.Setex(msgKey, redisValue, 3600*24*time.Second)
if err == nil {
log.Info("re =" + fmt.Sprint(re))
} else {
log.Info("set redis error:" + err.Error())
}
}

func asyncToExportDataset(dataset *models.Dataset, storeType int, modelSelectedFile string, aiTask *models.Cloudbrain, user *models.User, msgKey string, msgMap map[string]int, versionName string, description string) {
filterFiles := strings.Split(modelSelectedFile, ";")
for _, shortFile := range filterFiles {
uuid := uuid.NewV4()
id := uuid.String()
fileName := getFileName(shortFile)
log.Info("shortSrcFile=" + shortFile + " fileName=" + fileName)
if aiTask.ComputeResource == models.GPUResource || aiTask.ComputeResource == models.GCUResource {
size := getFileSizeFromMinio(aiTask.JobName, shortFile)
if isExistInAttachment(fileName, size, dataset, storeType) {
msgMap[shortFile] = -2
} else {
err := exportModelToDataFromMinio(id, aiTask.JobName, shortFile, storeType)
if err != nil {
msgMap[shortFile] = -1
} else {
msgMap[shortFile] = 100
finishedUploadAttachment(dataset.ID, size, id, fileName, description, storeType, user)
}
}

setProgress(msgKey, msgMap)
} else if aiTask.ComputeResource == models.NPUResource {
size := getFileSizeFromObs(aiTask.JobName, shortFile, versionName)
if isExistInAttachment(fileName, size, dataset, storeType) {
msgMap[shortFile] = -2
} else {
err := exportModelToDataFromObs(id, aiTask.JobName, shortFile, storeType, versionName)
if err != nil {
msgMap[shortFile] = -1
} else {
msgMap[shortFile] = 100
finishedUploadAttachment(dataset.ID, size, id, fileName, description, storeType, user)
}
}
setProgress(msgKey, msgMap)
} else {
log.Info("ExportModelToExistDataSet cannot suppport the ComputeResource" + aiTask.ComputeResource)
msgMap[shortFile] = -1
setProgress(msgKey, msgMap)
}
}
}

func isExistInAttachment(fileName string, size int64, dataset *models.Dataset, storeType int) bool {
err := models.GetDatasetAttachments(storeType, false, nil, dataset)
if err == nil && dataset.Attachments != nil && len(dataset.Attachments) > 0 {
for _, attach := range dataset.Attachments {
if attach.Name == fileName && attach.Size == size {
log.Info("Has found same file. so not to create.")
return true
}
}
} else {
log.Info("Not found attachments....")
if err != nil {
log.Info("error=" + err.Error())
}
}
return false
}

func getFileSizeFromObs(jobName string, modelSelectedFile string, versionName string) int64 {
objectkey := strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, versionName), "/")
modelSrcPrefix := objectkey + "/"
modelFile := modelSrcPrefix + modelSelectedFile
log.Info("modelfile=" + modelFile)
totalSize := storage.ObsGetFilesSize(setting.Bucket, []string{modelFile})
return totalSize
}

func exportModelToDataFromObs(id string, jobName string, modelSelectedFile string, storeType int, versionName string) error {
objectkey := strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, versionName), "/")
modelSrcPrefix := objectkey + "/"
modelFile := modelSrcPrefix + modelSelectedFile
log.Info("modelfile=" + modelFile)
//totalSize := storage.ObsGetFilesSize(setting.Bucket, []string{modelFile})

fileName := getFileName(modelSelectedFile)

if storeType == models.TypeCloudBrainOne {
reader, err := storage.ObsDownloadAFile(setting.Bucket, modelFile)
if err == nil {
defer reader.Close()
destPath := strings.TrimPrefix(path.Join(setting.Attachment.Minio.BasePath, path.Join(id[0:1], id[1:2], id)), "/")
log.Info("destPath=" + destPath)
bucketName := setting.Attachment.Minio.Bucket
_, minioErr := storage.Attachments.UploadContent(bucketName, destPath, reader)
if minioErr != nil {
log.Info("upload to minio failed 2.err=" + minioErr.Error())
return minioErr
}
} else {
log.Info("upload to minio failed 1.err=" + err.Error())
return err
}
} else {
destObjectKey := strings.TrimPrefix(path.Join(setting.BasePath, path.Join(id[0:1], id[1:2], id, fileName)), "/")
log.Info("destPath=" + destObjectKey)
log.Info("copy to bucket=" + setting.Bucket + " objectKey=" + destObjectKey)
obsErr := storage.ObsCopyFile(setting.Bucket, modelFile, setting.Bucket, destObjectKey)
if obsErr != nil {
log.Info("upload to obs failed.err=" + obsErr.Error())
return obsErr
}
}
return nil
}

func getFileSizeFromMinio(jobName string, modelSelectedFile string) int64 {
modelSrcPrefix := setting.CBCodePathPrefix + jobName + "/model/"
bucketName := setting.Attachment.Minio.Bucket
log.Info(" modelSrcPrefix=" + modelSrcPrefix + " bucket=" + bucketName)
modelFile := modelSrcPrefix + modelSelectedFile
totalSize := storage.MinioGetFilesSize(bucketName, []string{modelFile})
return totalSize
}

func exportModelToDataFromMinio(id string, jobName string, modelSelectedFile string, storeType int) error {
modelSrcPrefix := setting.CBCodePathPrefix + jobName + "/model/"
bucketName := setting.Attachment.Minio.Bucket
log.Info(" modelSrcPrefix=" + modelSrcPrefix + " bucket=" + bucketName)
modelFile := modelSrcPrefix + modelSelectedFile
//totalSize := storage.MinioGetFilesSize(bucketName, []string{modelFile})

fileName := getFileName(modelSelectedFile)
if storeType == models.TypeCloudBrainOne {
destPath := strings.TrimPrefix(path.Join(setting.Attachment.Minio.BasePath, path.Join(id[0:1], id[1:2], id)), "/")
log.Info("destPath=" + destPath)
_, minioErr := storage.MinioCopyAFile(bucketName, modelFile, bucketName, destPath)
if minioErr != nil {
log.Info("upload to minio failed.err=" + minioErr.Error())
return minioErr
}
} else {
reader, err := storage.Attachments.DownloadAFile(bucketName, modelFile)
if err == nil {
defer reader.Close()
destObjectKey := strings.TrimPrefix(path.Join(setting.BasePath, path.Join(id[0:1], id[1:2], id, fileName)), "/")
log.Info("destPath=" + destObjectKey)
log.Info("upload to bucket=" + setting.Bucket + " objectKey=" + destObjectKey)
obsErr := storage.PutReaderToObs(setting.Bucket, destObjectKey, reader)
if obsErr != nil {
log.Info("upload to obs failed 1.err=" + obsErr.Error())
return obsErr
}
} else {
log.Info("upload to obs failed 2.err=" + err.Error())
return err
}
}
return nil
}

func getFileName(shortFile string) string {
index := strings.LastIndex(shortFile, "/")
if index > 0 {
return shortFile[index+1:]
}
return shortFile
}

+ 47
- 12
routers/repo/grampus.go View File

@@ -1027,7 +1027,18 @@ func getPreTrainModelPath(pretrainModelDir string, fileName string) string {
} else {
return ""
}
}

func getPreTrainModelPaths(pretrainModelDir string, fileName []string) []string {
var paths []string
index := strings.Index(pretrainModelDir, "/")
if index > 0 {
filterBucket := pretrainModelDir[index+1:]
for _, name := range fileName {
paths = append(paths, filterBucket+name)
}
}
return paths
}

func GrampusTrainJobVersionCreate(ctx *context.Context, form auth.CreateGrampusTrainJobForm) {
@@ -1234,8 +1245,10 @@ func grampusTrainJobNpuCreate(ctx *context.Context, form auth.CreateGrampusTrain

}

ckptNames := strings.Split(form.CkptName, ";")

//prepare command
preTrainModelPath := getPreTrainModelPath(form.PreTrainModelUrl, form.CkptName)
preTrainModelPaths := getPreTrainModelPaths(form.PreTrainModelUrl, ckptNames)
command, err := generateCommand(repo.Name, grampus.ProcessorTypeNPU, bootFile, params, setting.CodePathPrefix+jobName+modelarts.OutputPath, datasetNames, form.CkptName, grampus.GetNpuModelRemoteObsUrl(jobName))
if err != nil {
log.Error("Failed to generateCommand: %s (%v)", displayJobName, err, ctx.Data["MsgID"])
@@ -1278,11 +1291,14 @@ func grampusTrainJobNpuCreate(ctx *context.Context, form auth.CreateGrampusTrain
req.ModelId = form.ModelId
req.ModelVersion = form.ModelVersion
req.PreTrainModelUrl = form.PreTrainModelUrl
req.PreTrainModelPath = preTrainModelPath
if !modelmanage.HasModelFileByModelId(req.ModelId, req.CkptName) { //使用预训练模型训练
grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeNPU)
ctx.RenderWithErr(ctx.Tr("repo.train.manage.model_not_exist"), tpl, &form)
return
req.PreTrainModelPaths = preTrainModelPaths
req.CkptNames = ckptNames
for _, ckptName := range req.CkptNames {
if !modelmanage.HasModelFileByModelId(req.ModelId, ckptName) { //使用预训练模型训练
grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeNPU)
ctx.RenderWithErr(ctx.Tr("repo.train.manage.model_not_exist"), tpl, &form)
return
}
}
}

@@ -1478,7 +1494,7 @@ func GrampusNotebookShow(ctx *context.Context) {
ctx.Data["task"] = task
ctx.Data["datasetDownload"] = getDatasetDownloadInfo(ctx, task)
ctx.Data["modelDownload"] = getModelDownloadInfo(ctx, task)
ctx.Data["canDownload"] = cloudbrain.CanModifyJob(ctx, task)
ctx.Data["canDownload"] = cloudbrain.CanDownloadJob(ctx, task)
ctx.Data["ai_center"] = cloudbrainService.GetAiCenterShow(task.AiCenter, ctx)
ctx.Data["code_path"] = cloudbrain.CodeMountPath
ctx.Data["dataset_path"] = cloudbrain.DataSetMountPath
@@ -1626,7 +1642,7 @@ func GrampusTrainJobShow(ctx *context.Context) {

ctx.Data["version_list_task"] = taskList
ctx.Data["datasetDownload"] = GetCloudBrainDataSetInfo(task.Uuid, task.DatasetName, false)
ctx.Data["canDownload"] = cloudbrain.CanModifyJob(ctx, task)
ctx.Data["canDownload"] = cloudbrain.CanDownloadJob(ctx, task)
ctx.Data["displayJobName"] = task.DisplayJobName
ctx.Data["canReschedule"] = cloudbrain.CanDeleteJob(ctx, task)

@@ -1752,15 +1768,34 @@ func GrampusDebugJobEvents(ctx *context.Context) {
return
}

var grampusJobEvent models.GrampusJobEvents
getJobResult, err := grampus.GetJob(job.JobID)
if err != nil {
log.Error("GetJob(%s) failed:%v", job.JobName, err)
}
if getJobResult != nil {
grampusJobEvent.Reason = getJobResult.ExitDiagnostics
}
jobExitEvent := models.GetGrampusDebugJobEventsResponse{
NotebookEvents: []models.GrampusJobEvents{
grampusJobEvent,
},
}

result, err := grampus.GetDebugJobEvents(job.JobID)
if err != nil {
log.Error("GetDebugJobEvents failed: %v", err, ctx.Data["MsgID"])
ctx.JSON(http.StatusOK, map[string]interface{}{
"JobID": ID,
"JobEvents": jobExitEvent,
})
return
}
result.NotebookEvents = append(result.NotebookEvents, grampusJobEvent)
ctx.JSON(http.StatusOK, map[string]interface{}{
"JobID": ID,
"JobEvents": result.NotebookEvents,
})

return
}

@@ -1993,10 +2028,10 @@ func GrampusCommitImageShow(ctx *context.Context) {
ctx.HTML(200, tplGrampusNotebookGPUImageShow)
}

func GrampusCommitImage(ctx *context.Context, form auth.CommitImageCloudBrainForm) {
func GrampusCommitImage(ctx *context.Context, form auth.CommitImageGrampusForm) {

if !NamePattern.MatchString(form.Tag) {
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("dataset.title_format_err")))
if !GrampusNamePattern.MatchString(form.Tag) {
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("repo.images.name_format_err")))
return
}



+ 62
- 41
routers/repo/modelarts.go View File

@@ -1254,7 +1254,7 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm)
Value: modelarts.Ascend,
})
}
datasUrlList, dataUrl, datasetNames, isMultiDataset, err := getDatasUrlListByUUIDS(uuid)
datasUrlList, dataUrl, datasetNames, _, err := getDatasUrlListByUUIDS(uuid)
if err != nil {
log.Error("Failed to getDatasUrlListByUUIDS: %v", err)
trainJobNewDataPrepare(ctx)
@@ -1269,18 +1269,14 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm)
ctx.RenderWithErr("json error:"+err.Error(), tplModelArtsTrainJobNew, &form)
return
}
if isMultiDataset {
param = append(param, models.Parameter{
Label: modelarts.MultiDataUrl,
Value: string(jsondatas),
})
}
// if isMultiDataset {
param = append(param, models.Parameter{
Label: modelarts.MultiDataUrl,
Value: string(jsondatas),
})
// }
if form.ModelName != "" { //使用预训练模型训练
ckptUrl := "/" + form.PreTrainModelUrl + form.CkptName
param = append(param, models.Parameter{
Label: modelarts.CkptUrl,
Value: "s3:/" + ckptUrl,
})
param = addModelUrlParam(param, form.PreTrainModelUrl, form.CkptName)
}

//save param config
@@ -1404,6 +1400,33 @@ func checkMultiNode(userId int64, serverNum int) string {
return "repo.modelarts.no_node_right"
}
}
func addModelUrlParam(param []models.Parameter, preTrainModelUrl string, ckptName string) []models.Parameter {
ckptNames := strings.Split(ckptName, ";")
ckptUrl := "/" + preTrainModelUrl + ckptNames[0]
param = append(param, models.Parameter{
Label: modelarts.CkptUrl,
Value: "s3:/" + ckptUrl,
})

var modelUrlList []models.ModelUrls
for _, ckptName := range ckptNames {
modelUrlList = append(modelUrlList, models.ModelUrls{
ModelUrl: "s3://" + preTrainModelUrl + ckptName,
ModelName: ckptName,
})
}
modelUrlsJson, err := json.Marshal(modelUrlList)
if err != nil {
log.Error("Failed to Marshal: %v", err)
return param
}
param = append(param, models.Parameter{
Label: modelarts.PretrainUrl,
Value: string(modelUrlsJson),
})
return param
}

func checkInferenceJobMultiNode(userId int64, serverNum int) string {
if serverNum == 1 {
return ""
@@ -1670,7 +1693,7 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ
})
}

datasUrlList, dataUrl, datasetNames, isMultiDataset, err := getDatasUrlListByUUIDS(uuid)
datasUrlList, dataUrl, datasetNames, _, err := getDatasUrlListByUUIDS(uuid)
if err != nil {
log.Error("Failed to getDatasUrlListByUUIDS: %v", err)
trainJobNewVersionDataPrepare(ctx)
@@ -1685,19 +1708,15 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ
ctx.RenderWithErr("json error:"+err.Error(), tplModelArtsTrainJobVersionNew, &form)
return
}
if isMultiDataset {
param = append(param, models.Parameter{
Label: modelarts.MultiDataUrl,
Value: string(jsondatas),
})
}
// if isMultiDataset {
param = append(param, models.Parameter{
Label: modelarts.MultiDataUrl,
Value: string(jsondatas),
})
// }

if form.ModelName != "" { //使用预训练模型训练
ckptUrl := "/" + form.PreTrainModelUrl + form.CkptName
param = append(param, models.Parameter{
Label: modelarts.CkptUrl,
Value: "s3:/" + ckptUrl,
})
param = addModelUrlParam(param, form.PreTrainModelUrl, form.CkptName)
}

if form.IsContinue { // qizhi NPU 继续训练,将旧任务输出文件拷贝至新任务输出路径
@@ -1994,7 +2013,7 @@ func TrainJobShow(ctx *context.Context) {
ctx.Data["version_list_task"] = VersionListTasks
ctx.Data["version_list_count"] = VersionListCount
ctx.Data["datasetList"] = datasetList
ctx.Data["canDownload"] = cloudbrain.CanModifyJob(ctx, &VersionListTasks[0].Cloudbrain)
ctx.Data["canDownload"] = cloudbrain.CanDownloadJob(ctx, &VersionListTasks[0].Cloudbrain)
ctx.HTML(http.StatusOK, tplModelArtsTrainJobShow)
}

@@ -2028,7 +2047,10 @@ func TrainJobDel(ctx *context.Context) {
_, err = modelarts.DelTrainJob(jobID)
if err != nil {
log.Error("DelTrainJob(%s) failed:%v", jobID, err.Error())
ctx.RenderWithErr(err.Error(), tplModelArtsTrainJobIndex, nil)
if err.Error() == "1" {
ctx.Flash.Error(ctx.Tr("deployment.deletion_notice_trainjob"))
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/train-job?listType=" + listType)
}
return
}

@@ -2299,11 +2321,10 @@ func InferenceJobCreate(ctx *context.Context, form auth.CreateModelArtsInference
param = append(param, models.Parameter{
Label: modelarts.ResultUrl,
Value: "s3:/" + resultObsPath,
}, models.Parameter{
Label: modelarts.CkptUrl,
Value: "s3:/" + ckptUrl,
})

param = addModelUrlParam(param, form.PreTrainModelUrl, form.CkptName)

datasUrlList, dataUrl, datasetNames, isMultiDataset, err := getDatasUrlListByUUIDS(uuid)
if err != nil {
inferenceJobErrorNewDataPrepare(ctx, form)
@@ -3029,18 +3050,18 @@ func getDatasUrlListByUUIDS(uuidStr string) ([]models.Datasurl, string, string,
return datasUrlList, dataUrl, datasetNames, isMultiDataset, errors.New("the dataset name is same")
}
}
if len(attachs) <= 1 {
dataUrl = "/" + setting.Bucket + "/" + setting.BasePath + path.Join(attach.UUID[0:1], attach.UUID[1:2]) + "/" + attach.UUID + attach.UUID + "/"
isMultiDataset = false
} else {
dataUrl = "/" + setting.Bucket + "/" + setting.BasePath + path.Join(attachs[0].UUID[0:1], attachs[0].UUID[1:2]) + "/" + attachs[0].UUID + attachs[0].UUID + "/"
datasetUrl := "s3://" + setting.Bucket + "/" + setting.BasePath + path.Join(attach.UUID[0:1], attach.UUID[1:2]) + "/" + attach.UUID + attach.UUID + "/"
datasUrlList = append(datasUrlList, models.Datasurl{
DatasetUrl: datasetUrl,
DatasetName: fileName,
})
isMultiDataset = true
}
// if len(attachs) <= 1 {
// dataUrl = "/" + setting.Bucket + "/" + setting.BasePath + path.Join(attach.UUID[0:1], attach.UUID[1:2]) + "/" + attach.UUID + attach.UUID + "/"
// isMultiDataset = false
// } else {
dataUrl = "/" + setting.Bucket + "/" + setting.BasePath + path.Join(attachs[0].UUID[0:1], attachs[0].UUID[1:2]) + "/" + attachs[0].UUID + attachs[0].UUID + "/"
datasetUrl := "s3://" + setting.Bucket + "/" + setting.BasePath + path.Join(attach.UUID[0:1], attach.UUID[1:2]) + "/" + attach.UUID + attach.UUID + "/"
datasUrlList = append(datasUrlList, models.Datasurl{
DatasetUrl: datasetUrl,
DatasetName: fileName,
})
// isMultiDataset = true
// }

if i == 0 {
datasetNames = attach.Name


+ 14
- 0
routers/repo/setting.go View File

@@ -489,6 +489,20 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
return
}
deployments, err := models.GetRunningServiceByUser(ctx.User.ID)
if err != nil {
ctx.ServerError("GetRunningServiceByUser", err)
return
}
if deployments != nil {
if len(deployments) > 0 {
ctx.Data["Err_RepoName"] = nil
log.Error("盘古部署删除项目失败,repo id %v, 用户 id%v", repo.ID, ctx.User.ID)
ctx.Flash.Error(ctx.Tr("deployment.deletion_notice_repo"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
return
}
}
count, err := models.GetCloudbrainRunCountByRepoID(repo.ID)
if err != nil {
ctx.ServerError("GetCloudbrainCountByRepoID failed", err)


+ 2
- 0
routers/repo/util.go View File

@@ -3,3 +3,5 @@ package repo
import "regexp"

var NamePattern = regexp.MustCompile(`^[A-Za-z0-9-_\\.]{1,100}$`)

var GrampusNamePattern = regexp.MustCompile(`^[A-Za-z][\w|\-|\\.]{0,49}$`)

+ 18
- 1
routers/routes/routes.go View File

@@ -381,6 +381,16 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/wenxin/paint_new", reqSignIn, modelapp.WenXinPaintNew)
m.Get("/wenxin/query_paint_result", reqSignIn, modelapp.QueryWenXinPaintResult)
m.Get("/wenxin/query_paint_image", reqSignIn, modelapp.QueryWenXinPaintById)
m.Group("/modelbase", func() {
m.Get("", modelapp.ModelBaseUI)
m.Group("/pangufinetune", func() {
m.Get("", modelapp.PanguFinetuneUI)
m.Get("/create", reqSignIn, modelapp.PanguFinetuneCreateUI)
m.Get("/inference", reqSignIn, modelapp.PanguInferenceUI)
})

})

})

m.Group("/explore", func() {
@@ -1226,6 +1236,13 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/label", func() {
m.Get("/:uuid", reqRepoDatasetReader, repo.LabelIndex)
})
m.Group("/model", func() {
m.Get("/getcurrentdataset", reqRepoDatasetReader, repo.GetCurrentDataSet)
m.Get("/getmodelfile", reqRepoDatasetReader, repo.GetDataSetSelectItemByJobId)
m.Get("/getprogress", reqRepoDatasetReader, repo.GetExportDataSetByMsgId)
m.Post("/export_exist_dataset", reqRepoDatasetWriterJson, repo.ExportModelToExistDataSet)
})

}, context.RepoRef())

m.Group("/cloudbrain", func() {
@@ -1291,7 +1308,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("", reqRepoCloudBrainReader, repo.GrampusNotebookShow)
m.Get("/debug", reqWechatBind, cloudbrain.AdminOrJobCreaterRight, repo.GrampusNotebookDebug)
m.Get("/commit_image", cloudbrain.AdminOrJobCreaterRight, repo.GrampusCommitImageShow)
m.Post("/commit_image", cloudbrain.AdminOrJobCreaterRight, bindIgnErr(auth.CommitImageCloudBrainForm{}), repo.GrampusCommitImage)
m.Post("/commit_image", cloudbrain.AdminOrJobCreaterRight, bindIgnErr(auth.CommitImageGrampusForm{}), repo.GrampusCommitImage)
m.Post("/restart", reqWechatBind, cloudbrain.AdminOrJobCreaterRight, repo.GrampusNotebookRestart)
m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.GrampusStopJob)
m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.GrampusNotebookDel)


+ 3
- 0
routers/search.go View File

@@ -1263,6 +1263,9 @@ func searchModel(ctx *context.Context, TableName string, Key string, Page int, P
log.Info("actor is null?:" + fmt.Sprint(ctx.User == nil))
sortBy := "ai_model_manage.reference_count desc,ai_model_manage.download_count desc,ai_model_manage.created_unix desc"
if SortBy != "" && SortBy != "default" {
if strings.HasSuffix(SortBy, ".keyword") {
SortBy = SortBy[0:(len(SortBy) - len(".keyword"))]
}
sortBy = "ai_model_manage." + SortBy
if ascending {
sortBy += " asc"


+ 2
- 2
services/ai_task_service/task/cloudbrain_one_notebook_task.go View File

@@ -75,7 +75,7 @@ func (g CloudbrainOneNotebookTaskTemplate) CallCreationAPI(ctx *context.Creation
PreTrainModel: ctx.GetContainerDataArray(ai_task_entity.ContainerPreTrainModel),
AutoStopDuration: autoStopDurationMs,
Capacity: setting.Capacity,
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID),
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID, form.JobType),
Spec: ctx.Spec,
},
},
@@ -118,7 +118,7 @@ func (g CloudbrainOneNotebookTaskTemplate) CallRestartAPI(ctx *context.CreationC
PreTrainModel: ctx.GetContainerDataArray(ai_task_entity.ContainerPreTrainModel),
AutoStopDuration: autoStopDurationMs,
Capacity: setting.Capacity,
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID),
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID, form.JobType),
Spec: ctx.Spec,
},
},


+ 1
- 1
services/ai_task_service/task/grampus_notebook_task.go View File

@@ -101,7 +101,7 @@ func (g GrampusNoteBookTaskTemplate) CallCreationAPI(ctx *context.CreationContex
Code: ctx.GetContainerDataArray(ai_task_entity.ContainerCode),
AutoStopDuration: autoStopDurationMs,
Capacity: setting.Capacity,
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID),
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID, form.JobType),
Spec: ctx.Spec,
},
},


+ 2
- 2
services/ai_task_service/task/grampus_train_task.go View File

@@ -63,7 +63,7 @@ func (g GrampusTrainTaskTemplate) CallCreationAPI(ctx *context.CreationContext)
ImageUrl: strings.TrimSpace(form.ImageUrl),
Datasets: ctx.GetContainerDataArray(ai_task_entity.ContainerDataset),
Code: ctx.GetContainerData(ai_task_entity.ContainerCode),
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID),
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID, form.JobType),
Models: ctx.GetContainerDataArray(ai_task_entity.ContainerPreTrainModel),
BootFile: form.BootFile,
OutPut: ctx.GetContainerData(ai_task_entity.ContainerOutPutPath),
@@ -106,7 +106,7 @@ func (g GrampusTrainTaskTemplate) CallRestartAPI(ctx *context.CreationContext) *
ImageUrl: strings.TrimSpace(form.ImageUrl),
Datasets: ctx.GetContainerDataArray(ai_task_entity.ContainerDataset),
Code: ctx.GetContainerData(ai_task_entity.ContainerCode),
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID),
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID, form.JobType),
Models: ctx.GetContainerDataArray(ai_task_entity.ContainerPreTrainModel),
BootFile: form.BootFile,
OutPut: ctx.GetContainerData(ai_task_entity.ContainerOutPutPath),


+ 15
- 7
services/cloudbrain/cloudbrainTask/train.go View File

@@ -178,11 +178,15 @@ func ModelArtsTrainJobNpuCreate(ctx *context.Context, option api.CreateTrainJobO
if err == nil {
os.RemoveAll(codeLocalPath)
}
var codeDownloadRepo = repo
if option.CodeRepo != nil {
codeDownloadRepo = option.CodeRepo.(*models.Repository)
}

gitRepo, _ := git.OpenRepository(repo.RepoPath())
gitRepo, _ := git.OpenRepository(codeDownloadRepo.RepoPath())
commitID, _ := gitRepo.GetBranchCommitID(branchName)

if err := downloadCode(repo, codeLocalPath, branchName); err != nil {
if err := downloadCode(codeDownloadRepo, codeLocalPath, branchName); err != nil {
log.Error("downloadCode failed, server timed out: %s (%v)", repo.FullName(), err)
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("cloudbrain.load_code_failed")))
return
@@ -289,6 +293,9 @@ func ModelArtsTrainJobNpuCreate(ctx *context.Context, option api.CreateTrainJobO
TotalVersionCount: modelarts.TotalVersionCount,
DatasetName: datasetNames,
Spec: spec,
FineTune: option.FineTune,
FineTuneModelType: option.FineTuneModelType,
FineTuneCategory: option.FineTuneCategory,
}
if option.ModelName != "" { //使用预训练模型训练
req.ModelName = option.ModelName
@@ -486,11 +493,12 @@ func checkParameters(ctx *context.Context, option api.CreateTrainJobOption, lock
if !jobNamePattern.MatchString(option.DisplayJobName) {
return nil, nil, "", fmt.Errorf(ctx.Tr("repo.cloudbrain_jobname_err"))
}

bootFileExist, err := ctx.Repo.FileExists(option.BootFile, option.BranchName)
if err != nil || !bootFileExist {
log.Error("Get bootfile error:", err, ctx.Data["MsgID"])
return nil, nil, "", fmt.Errorf(ctx.Tr("repo.cloudbrain_bootfile_err"))
if option.CodeRepo == nil {
bootFileExist, err := ctx.Repo.FileExists(option.BootFile, option.BranchName)
if err != nil || !bootFileExist {
log.Error("Get bootfile error:", err, ctx.Data["MsgID"])
return nil, nil, "", fmt.Errorf(ctx.Tr("repo.cloudbrain_bootfile_err"))
}
}

count, err := GetNotFinalStatusTaskCount(ctx.User.ID, string(models.JobTypeTrain))


+ 15
- 0
services/cloudbrain/resource/resource_specification.go View File

@@ -147,6 +147,21 @@ func GetAllDistinctResourceSpecification(opts models.SearchResourceSpecification
return models.NewResourceSpecAndQueueListRes(0, nr), nil
}

func GetAllResourceSpecification(opts models.SearchResourceSpecificationOptions) ([]*models.ResourceSpecInfo, error) {
opts.Page = 0
opts.PageSize = 1000
opts.OrderBy = models.SearchSpecOrder4Standard
_, r, err := models.SearchResourceSpecification(opts)
if err != nil {
return nil, err
}
res := make([]*models.ResourceSpecInfo, len(r))
for i := 0; i < len(r); i++ {
res[i] = r[i].ConvertToResourceSpecInfo()
}
return res, nil
}

func distinctResourceSpecAndQueue(r []models.ResourceSpecAndQueue) []models.ResourceSpecAndQueue {
specs := make([]models.ResourceSpecAndQueue, 0, len(r))
sourceSpecIdMap := make(map[string]models.ResourceSpecAndQueue, 0)


+ 1
- 1
templates/admin/cloudbrain/list.tmpl View File

@@ -284,7 +284,7 @@
{{end}}
</div>
<!-- 修改任务 -->
{{if eq .JobType "TRAIN"}}
{{if and (eq .JobType "TRAIN") (not .FineTune)}}
<div class="ui compact buttons __btn_edit__">
<a style="padding: 0.5rem 1rem;" class="ui basic blue button" href="{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .Cloudbrain.Type 1}}/modelarts/train-job/{{.JobID}}{{else if eq .Cloudbrain.Type 0}}/cloudbrain/train-job/{{.JobID}}{{else if eq .Cloudbrain.Type 2}}/grampus/train-job/{{.JobID}}{{end}}/create_version{{if .VersionName}}?version_name={{.VersionName}}{{end}}">
{{$.i18n.Tr "repo.modelarts.modify"}}


+ 1
- 1
templates/base/footer_content.tmpl View File

@@ -67,7 +67,7 @@
{{else}}
<a href="{{AppSubUrl}}/user/login?redirect_to={{AppSubUrl}}/zeizei/OpenI_Learning/issues/new" class="item"><i class="envelope icon"></i> {{.i18n.Tr "custom.foot.advice_feedback"}}</a>
{{end}}
<a href="{{AppSubUrl}}/resource_desc" class="item" target="_blank"><i class="server icon"></i> {{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" class="item" target="_blank"><i class="server icon"></i> {{.i18n.Tr "custom.resource_description"}}</a>
{{template "custom/extra_links_footer" .}}
</div>


+ 2
- 1
templates/base/footer_content_fluid.tmpl View File

@@ -53,8 +53,9 @@
{{else}}
<a href="{{AppSubUrl}}/user/login" class="item"><i class="envelope icon"></i> {{.i18n.Tr "custom.foot.advice_feedback"}}</a>
{{end}}
<a href="{{AppSubUrl}}/resource_desc" class="item" target="_blank"><i class="server icon"></i> {{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" class="item" target="_blank"><i class="server icon"></i> {{.i18n.Tr "custom.resource_description"}}</a>
{{template "custom/extra_links_footer" .}}
</div>
</div>
</div>


+ 1
- 1
templates/base/head.tmpl View File

@@ -37,7 +37,7 @@
<meta name="referrer" content="strict-origin-when-cross-origin" />
<meta name="_csrf" content="{{.CsrfToken}}" />
{{if .IsSigned}}
<meta name="_uid" content="{{.SignedUser.ID}}" />
<meta name="_uid" content="{{.SignedUser.ID}}" content-ext="{{ .SignedUser.Name }}" />
{{end}}
{{if .ContextUser}}
<meta name="_context_uid" content="{{.ContextUser.ID}}" />


+ 1
- 1
templates/base/head_course.tmpl View File

@@ -37,7 +37,7 @@
<meta name="referrer" content="strict-origin-when-cross-origin" />
<meta name="_csrf" content="{{.CsrfToken}}" />
{{if .IsSigned}}
<meta name="_uid" content="{{.SignedUser.ID}}" />
<meta name="_uid" content="{{.SignedUser.ID}}" content-ext="{{ .SignedUser.Name }}" />
{{end}}
{{if .ContextUser}}
<meta name="_context_uid" content="{{.ContextUser.ID}}" />


+ 1
- 1
templates/base/head_fluid.tmpl View File

@@ -37,7 +37,7 @@
<meta name="referrer" content="strict-origin-when-cross-origin" />
<meta name="_csrf" content="{{.CsrfToken}}" />
{{if .IsSigned}}
<meta name="_uid" content="{{.SignedUser.ID}}" />
<meta name="_uid" content="{{.SignedUser.ID}}" content-ext="{{ .SignedUser.Name }}" />
{{end}}
{{if .ContextUser}}
<meta name="_context_uid" content="{{.ContextUser.ID}}" />


+ 1
- 1
templates/base/head_home.tmpl View File

@@ -37,7 +37,7 @@
<meta name="referrer" content="strict-origin-when-cross-origin" />
<meta name="_csrf" content="{{.CsrfToken}}" />
{{if .IsSigned}}
<meta name="_uid" content="{{.SignedUser.ID}}" />
<meta name="_uid" content="{{.SignedUser.ID}}" content-ext="{{ .SignedUser.Name }}" />
{{end}}
{{if .ContextUser}}
<meta name="_context_uid" content="{{.ContextUser.ID}}" />


+ 4
- 2
templates/base/head_navbar.tmpl View File

@@ -42,7 +42,8 @@
{{.i18n.Tr "repo.model_manager"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/modelbase">{{.i18n.Tr "repo.model_base"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/wenxin">{{.i18n.Tr "repo.model_experience"}}</a>
</div>
</div>
@@ -86,7 +87,8 @@
{{.i18n.Tr "repo.model_manager"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/modelbase">{{.i18n.Tr "repo.model_base"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/wenxin">{{.i18n.Tr "repo.model_experience"}}</a>
</div>
</div>


+ 4
- 2
templates/base/head_navbar_fluid.tmpl View File

@@ -39,7 +39,8 @@
{{.i18n.Tr "repo.model_manager"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/modelbase">{{.i18n.Tr "repo.model_base"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/wenxin">{{.i18n.Tr "repo.model_experience"}}</a>
</div>
</div>
@@ -81,7 +82,8 @@
{{.i18n.Tr "repo.model_manager"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/modelbase">{{.i18n.Tr "repo.model_base"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/wenxin">{{.i18n.Tr "repo.model_experience"}}</a>
</div>
</div>


+ 4
- 2
templates/base/head_navbar_home.tmpl View File

@@ -31,7 +31,8 @@
{{.i18n.Tr "repo.model_manager"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/modelbase">{{.i18n.Tr "repo.model_base"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/wenxin">{{.i18n.Tr "repo.model_experience"}}</a>
</div>
</div>
@@ -75,7 +76,8 @@
{{.i18n.Tr "repo.model_manager"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/modelbase">{{.i18n.Tr "repo.model_base"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/wenxin">{{.i18n.Tr "repo.model_experience"}}</a>
</div>
</div>


+ 4
- 2
templates/base/head_navbar_pro.tmpl View File

@@ -41,7 +41,8 @@
{{.i18n.Tr "repo.model_manager"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/modelbase">{{.i18n.Tr "repo.model_base"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/wenxin">{{.i18n.Tr "repo.model_experience"}}</a>
</div>
</div>
@@ -85,7 +86,8 @@
{{.i18n.Tr "repo.model_manager"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/modelsquare/main">{{.i18n.Tr "repo.model_square"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/modelbase">{{.i18n.Tr "repo.model_base"}}</a>
<a class="item" href="{{AppSubUrl}}/extension/wenxin">{{.i18n.Tr "repo.model_experience"}}</a>
</div>
</div>


+ 72
- 0
templates/custom/export_dataset.tmpl View File

@@ -0,0 +1,72 @@
<div id="newdataset">
<div class="ui modal export_dataset">
<div id="container">
<div class="header" style="padding: 1rem;background-color: rgba(240, 240, 240, 100);">
<h4 id="model_header">{{$.i18n.Tr "repo.export_result_to_dataset"}}</h4>
</div>
<div class="content content-padding">
<div style="text-align: center;padding-bottom: 2rem;">
<span class="text-tip">{{$.i18n.Tr "dataset.export_tips" | Safe}}</span>
</div>
<form class="ui form">
<div class="ui error message"></div>
{{.CsrfTokenHtml}}
<input type="hidden" name="type" value="0">
<input type="hidden" name="modelSelectedFile">
<div class="inline field">
<label class="label-fix-width">{{$.i18n.Tr "dataset.dataset_available_clusters"}}</label>
<div class="ui blue mini menu compact selectcloudbrain" id="export-dataset-type">
<a class="active item" data-type="0">
<svg class="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M3 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z"/>
</svg>
CPU/GPU
</a>
<a class="item" data-type="1">
<svg class="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M3 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z"/>
</svg>
NPU
</a>
</div>
</div>
<div class="inline field">
<label class="label-fix-width">{{$.i18n.Tr "dataset.file_description"}}:</label>
<textarea style="width: 80%;" id="description" name="description" placeholder='{{.i18n.Tr "repo.modelarts.train_job.new_place"}}' rows="3" maxlength="255" placeholder="" onchange="this.value=this.value.substring(0, 255)" onkeydown="this.value=this.value.substring(0, 255)" onkeyup="this.value=this.value.substring(0, 255)"></textarea>
</div>
<div class="inline field">
<label class="label-fix-width"></label>
<div id="export-dataset-file">
<div class="ui items" id="model-file-export">
</div>
</div>
</div>
<div class="inline field">
<label class="label-fix-width">{{$.i18n.Tr "dataset.select_result_file"}}</label>
<span id="export-dataset-select">
<i class="plus square outline icon"></i>{{$.i18n.Tr "dataset.select_file"}}
<div id="model-file-wrap" style="display:none">
<div class="ui list" id="model-file-result" >
</div>
</div>
</span>
</div>
</form>
<div class="actions" style="margin-left: 140px;margin-top: 2rem;">
<button type="button" class="ui approve green button">{{$.i18n.Tr "dataset.export_file"}}</button>
<button type="button" class="ui button cancel">{{$.i18n.Tr "cancel"}}</button>
</div>
</div>
</div>
</div>
<div class="ui modal no_export_dataset">
<div class="item-empty">
<div class="item-empty-icon"></div>
<div class="item-empty-tips">{{.i18n.Tr "dataset.dataset_no_create"}} <a href="/{{$.RepoRelPath}}/datasets/create">{{$.i18n.Tr "dataset.go_new_dataset"}}</a></div>
</div>
</div>
</div>

+ 1
- 1
templates/custom/home/home_top.tmpl View File

@@ -1631,7 +1631,7 @@
{{.i18n.Tr "custom.Platform_Tutorial"}}
</div>
</a>
<a href="/resource_desc">
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">
<div class="_hm-big-btn-2" style="margin-right:0;">
<svg xmlns="http://www.w3.org/2000/svg" style="margin-right:8px;margin-top:-2px;" class="styles__StyledSVGIconPathComponent-sc-16fsqc8-0 fPsHiw svg-icon-path-icon fill" viewBox="0 0 32 32" width="16" height="16"><defs data-reactroot=""></defs><g><path fill="rgb(255, 255, 255)" d="M16 29.333c-7.364 0-13.333-5.969-13.333-13.333s5.969-13.333 13.333-13.333 13.333 5.969 13.333 13.333-5.969 13.333-13.333 13.333zM12.947 26.223c-1.254-2.597-2.063-5.628-2.241-8.827l-0.003-0.062h-5.287c0.551 4.238 3.518 7.661 7.455 8.869l0.076 0.020zM13.373 17.333c0.201 3.252 1.131 6.307 2.627 9.003 1.469-2.597 2.418-5.669 2.624-8.941l0.003-0.061h-5.253zM26.584 17.333h-5.287c-0.181 3.261-0.99 6.292-2.306 9.033l0.062-0.143c4.012-1.229 6.98-4.651 7.525-8.836l0.006-0.054zM5.416 14.667h5.287c0.181-3.261 0.99-6.292 2.306-9.033l-0.062 0.143c-4.012 1.229-6.98 4.652-7.525 8.836l-0.006 0.054zM13.375 14.667h5.251c-0.209-3.334-1.157-6.405-2.68-9.109l0.055 0.106c-1.469 2.597-2.418 5.669-2.624 8.941l-0.003 0.061zM19.053 5.777c1.254 2.597 2.063 5.628 2.241 8.827l0.003 0.062h5.287c-0.551-4.238-3.518-7.661-7.455-8.869l-0.076-0.020z"></path></g></svg>
{{.i18n.Tr "custom.resource_description"}}


+ 5
- 0
templates/model/base/index.tmpl View File

@@ -0,0 +1,5 @@
{{template "base/head_home" .}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-modelbase-home.css?v={{MD5 AppVer}}" />
<div id="__vue-root"></div>
<script src="{{StaticUrlPrefix}}/js/vp-modelbase-home.js?v={{MD5 AppVer}}"></script>
{{template "base/footer" .}}

+ 6
- 0
templates/model/base/panguCreate.tmpl View File

@@ -0,0 +1,6 @@
{{template "base/head_home" .}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-modelbase-create.css?v={{MD5 AppVer}}" />
<script>window.notStopTaskCount = {{.NotStopTaskCount}};</script>
<div id="__vue-root"></div>
<script src="{{StaticUrlPrefix}}/js/vp-modelbase-create.js?v={{MD5 AppVer}}"></script>
{{template "base/footer" .}}

+ 5
- 0
templates/model/base/panguIndex.tmpl View File

@@ -0,0 +1,5 @@
{{template "base/head_home" .}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-modelbase-model.css?v={{MD5 AppVer}}" />
<div id="__vue-root"></div>
<script src="{{StaticUrlPrefix}}/js/vp-modelbase-model.js?v={{MD5 AppVer}}"></script>
{{template "base/footer" .}}

+ 5
- 0
templates/model/base/panguInference.tmpl View File

@@ -0,0 +1,5 @@
{{template "base/head_home" .}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-modelbase-inference.css?v={{MD5 AppVer}}" />
<div id="__vue-root"></div>
<script src="{{StaticUrlPrefix}}/js/vp-modelbase-inference.js?v={{MD5 AppVer}}"></script>
{{template "base/footer" .}}

+ 2
- 2
templates/repo/cloudbrain/benchmark/new.tmpl View File

@@ -105,7 +105,7 @@
name="spec_id">
</select>
<span><i class="question circle icon link" data-content="{{.i18n.Tr "repo.modelarts.train_job.resource_helper"}}" data-position="right center" data-variation="mini"></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>
@@ -197,7 +197,7 @@
name="spec_id">
</select>
<span><i class="question circle icon link" data-content="{{.i18n.Tr "repo.modelarts.train_job.resource_helper"}}" data-position="right center" data-variation="mini"></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>


+ 1
- 1
templates/repo/cloudbrain/benchmark/show.tmpl View File

@@ -253,7 +253,7 @@
</td>

<td class="ti-text-form-content">
<div class="text-span text-span-w">{{if .CkptName}}{{.CkptName}}{{else}}--{{end}}</div>
<div class="text-span text-span-w" {{if .CkptName}}title="{{.CkptName}}"{{end}}>{{if .CkptName}}{{.CkptName}}{{else}}--{{end}}</div>
</td>
</tr>
{{end}}


+ 9
- 3
templates/repo/cloudbrain/image/edit.tmpl View File

@@ -39,13 +39,19 @@
{{$.i18n.Tr "cloudbrain.resource_cluster_openi_simple"}} GPU
{{end}}
</div>
<input type="hidden" value="{{.Type}}" name="type">
<input type="hidden" value="{{.Image.CloudbrainType}}" name="type">
</div>
<div class="inline required field">
<label class="label_color" for="">{{$.i18n.Tr "repo.images.name"}}</label>
<input type="hidden" name="tag" value="{{.Image.Tag}}" >
<input disabled value="{{.Image.Tag}}" style="width: 80%;">
<span class="tooltips" style="display: block;padding-left: 1.5rem;">{{.i18n.Tr "repo.images.name_rule"}}</span>
<input disabled value="{{.Image.Tag}}" style="width: 80%;">
<span class="tooltips" style="display: block;padding-left: 1.5rem;">
{{if eq .Image.CloudbrainType 2}}
{{.i18n.Tr "repo.images.name_rule50"}}
{{else}}
{{.i18n.Tr "repo.images.name_rule100"}}
{{end}}
</span>
</div>
<div class="inline required field">
<label class="label_color" for="">{{$.i18n.Tr "dataset.description"}}</label>


+ 8
- 2
templates/repo/cloudbrain/image/submit.tmpl View File

@@ -47,8 +47,14 @@
</div>
<div class="inline required field">
<label class="label_color" for="">{{$.i18n.Tr "repo.images.name"}}</label>
<input type="text" name="tag" required placeholder="{{$.i18n.Tr "repo.images.name_placerholder"}}" style="width: 80%;" maxlength="100">
<span class="tooltips" style="display: block;padding-left: 1.5rem;">{{.i18n.Tr "repo.images.name_rule"}}</span>
<input type="text" name="tag" required placeholder="{{$.i18n.Tr "repo.images.name_placerholder"}}" style="width: 80%;" maxlength="{{if eq .Type 2}} 50 {{else}} 100 {{end}}">
<span class="tooltips" style="display: block;padding-left: 1.5rem;">
{{if eq .Type 2}}
{{.i18n.Tr "repo.images.name_rule50"}}
{{else}}
{{.i18n.Tr "repo.images.name_rule100"}}
{{end}}
</span>
</div>
<div class="inline required field">
<label class="label_color" for="">{{$.i18n.Tr "dataset.description"}}</label>


+ 1
- 1
templates/repo/cloudbrain/inference/new.tmpl View File

@@ -261,7 +261,7 @@
<select id="__specs__" class="ui search dropdown width48" placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" ovalue="{{.spec_id}}" name="spec_id" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}>
</select>
<span><i class="question circle icon link" data-content="{{.i18n.Tr "repo.modelarts.train_job.resource_helper"}}" data-position="right center" data-variation="mini"></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>


+ 1
- 1
templates/repo/cloudbrain/new.tmpl View File

@@ -162,7 +162,7 @@
name="spec_id">
</select>
<span><i class="question circle icon link" data-content="{{.i18n.Tr "repo.modelarts.train_job.resource_helper"}}" data-position="right center" data-variation="mini"></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip" style="padding:0 5px;margin:6px 0;margin-left:154px;font-size:12px;width:48.5%!important;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>


+ 1
- 1
templates/repo/cloudbrain/show.tmpl View File

@@ -253,7 +253,7 @@
</td>

<td class="ti-text-form-content">
<div class="text-span text-span-w">{{if .CkptName}}{{.CkptName}}{{else}}--{{end}}</div>
<div class="text-span text-span-w" {{if .CkptName}}title="{{.CkptName}}"{{end}}>{{if .CkptName}}{{.CkptName}}{{else}}--{{end}}</div>
</td>
</tr>
<tr class="ti-no-ng-animate">


+ 1
- 1
templates/repo/cloudbrain/trainjob/new.tmpl View File

@@ -211,7 +211,7 @@
name="spec_id">
</select>
<span><i class="question circle icon link" data-content="{{.i18n.Tr "repo.modelarts.train_job.resource_helper"}}" data-position="right center" data-variation="mini" ></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>


+ 13
- 3
templates/repo/cloudbrain/trainjob/show.tmpl View File

@@ -56,13 +56,22 @@
<span class="accordion-panel-title-content">
<span>
<div style="float: right;">
{{if and ($.canDownload) (ne .Status "WAITING") ($.Permission.CanWrite $.UnitTypeModelManage) }}
{{if and ($.canDownload) (ne .Status "WAITING") }}
<a class="ti-action-menu-item" id="{{.VersionName}}-create-model"
onclick="showcreate({DisplayJobName:{{.DisplayJobName}},JobName:{{.JobName}},JobID:{{.JobID}},VersionName:{{.VersionName}}})">{{$.i18n.Tr "repo.modelarts.create_model"}}</a>
{{else}}
<a class="ti-action-menu-item disabled" id="{{.VersionName}}-create-model">{{$.i18n.Tr "repo.modelarts.create_model"}}</a>
{{end}}

{{if and ($.canDownload) (ne .Status "WAITING") }}
<a class="ti-action-menu-item export-dataset" style="position:relative" id="{{.VersionName}}-export-dataset" data-version="{{.VersionName}}" data-jobid="{{.JobID}}" data-repopath="/{{$.RepoRelPath}}/datasets/model">
{{$.i18n.Tr "repo.export_result_to_dataset"}}
<div class="export-popup" id="{{.VersionName}}-popup">
<div class="ui active centered inline loader" style="width: 100%;display: flex;align-items: center;">{{$.i18n.Tr "repo.loader_result_file"}}</div>
</div>
</a>
{{else}}
<a class="ti-action-menu-item disabled" id="{{.VersionName}}-export-dataset">{{$.i18n.Tr "repo.export_result_to_dataset"}}</a>
{{end}}
</div>
<div class="ac-display-inblock title_text acc-margin-bottom">
<span class="cti-mgRight-sm">{{TimeSinceUnix1 .CreatedUnix}}</span>
@@ -187,7 +196,7 @@
</td>

<td class="ti-text-form-content">
<div class="text-span text-span-w">{{if .CkptName}}{{.CkptName}}{{else}}--{{end}}</div>
<div class="text-span text-span-w" {{if .CkptName}}title="{{.CkptName}}"{{end}}>{{if .CkptName}}{{.CkptName}}{{else}}--{{end}}</div>
</td>
</tr>
@@ -512,6 +521,7 @@
</div>
</div>
</div>
{{template "custom/export_dataset" .}}
</div>
{{template "base/footer" .}}
<script type="text/javascript" src="/self/ztree/js/jquery.ztree.core.js?v={{MD5 AppVer}}"></script>


+ 1
- 1
templates/repo/grampus/notebook/gcu/new.tmpl View File

@@ -131,7 +131,7 @@
{{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}
placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' name="spec_id"></select>
<span><i class="question circle icon link" data-content="{{.i18n.Tr "repo.modelarts.train_job.resource_helper"}}" data-position="right center" data-variation="mini"></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip" style="padding:0 5px;margin:6px 0;margin-left:154px;font-size:12px;width:48.5%!important;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>


+ 1
- 1
templates/repo/grampus/notebook/gpu/new.tmpl View File

@@ -136,7 +136,7 @@
name="spec_id">
</select>
<span><i class="question circle icon link" data-content="{{.i18n.Tr "repo.modelarts.train_job.resource_helper"}}" data-position="right center" data-variation="mini"></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip" style="padding:0 5px;margin:6px 0;margin-left:154px;font-size:12px;width:48.5%!important;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>


+ 1
- 1
templates/repo/grampus/notebook/npu/new.tmpl View File

@@ -139,7 +139,7 @@
{{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}
placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' name="spec_id"></select>
<span><i class="question circle icon link" data-content="{{.i18n.Tr "repo.modelarts.train_job.resource_helper"}}" data-position="right center" data-variation="mini"></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip" style="padding:0 5px;margin:6px 0;margin-left:154px;font-size:12px;width:48.5%!important;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>


+ 17
- 1
templates/repo/grampus/notebook/show.tmpl View File

@@ -52,6 +52,7 @@
<div class="content-pad">
<div class="ui pointing secondary menu" style="border-bottom: 1px solid rgba(34,36,38,.15);">
<a class="active item" data-tab="first">{{$.i18n.Tr "repo.modelarts.train_job.config"}}</a>
<a class="item run_info" data-tab="five" data-version="{{.VersionName}}">{{$.i18n.Tr "repo.cloudbrain.runinfo"}}</a>
</div>
<div class="ui tab active" data-tab="first">
<div style="padding-top: 10px;">
@@ -261,7 +262,7 @@
</td>

<td class="ti-text-form-content">
<div class="text-span text-span-w">{{if .CkptName}}{{.CkptName}}{{else}}--{{end}}</div>
<div class="text-span text-span-w" {{if .CkptName}}title="{{.CkptName}}"{{end}}>{{if .CkptName}}{{.CkptName}}{{else}}--{{end}}</div>
</td>
</tr>
@@ -390,7 +391,21 @@
</div>
</div>
</div>
<div class="ui tab" data-tab="five">
<div style="position: relative;border: 1px solid rgba(0,0,0,.2);padding: 0 10px;margin-top: 10px;">
<div class="ui attached info" id="info{{.VersionName}}"
style="height: 300px !important; overflow: auto;">
<div class="ui inverted active dimmer">
<div class="ui loader"></div>
</div>
<span class="info_text">
</span>
</div>

</div>

</div>

</div>
</div>
@@ -424,6 +439,7 @@
{{template "base/footer" .}}
<script src="{{StaticUrlPrefix}}/js/specsuse.js?v={{MD5 AppVer}}" type="text/javascript"></script>
<script>
$('.menu .item').tab()
;(function() {
var SPEC = {{ .Spec }};
var showPoint = false;


+ 1
- 1
templates/repo/grampus/trainjob/gcu/new.tmpl View File

@@ -194,7 +194,7 @@
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label>
<select class="ui dropdown width48" id="__specs__" style='width:385px' name="spec_id" ovalue="{{.spec_id}}" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}></select>
<span><i class="question circle icon link"></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>


+ 1
- 1
templates/repo/grampus/trainjob/gpu/new.tmpl View File

@@ -200,7 +200,7 @@
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label>
<select class="ui dropdown width48" id="__specs__" style='width:385px' name="spec_id" ovalue="{{.spec_id}}" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}></select>
<span><i class="question circle icon link" data-content="{{.i18n.Tr "repo.modelarts.train_job.resource_helper"}}" data-position="right center" data-variation="mini"></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>


+ 4
- 4
templates/repo/grampus/trainjob/npu/new.tmpl View File

@@ -60,8 +60,8 @@
<label class="label-fix-width" style="font-weight: normal;"></label>
{{template "custom/task_wait_count" .}}
<div style="display: flex;align-items: center;margin-left: 156px;margin-top: 0.5rem;">
<i class="ri-error-warning-line" style="color: #f2711c;margin-right: 0.5rem;"></i>
<span style="color: #888;font-size: 12px;">{{.i18n.Tr "cloudbrain.new_train_npu_tooltips" "/cache/code" "ckpt_url" "/cache/output" | Safe}}</span>
<i class="ri-error-warning-line" style="color: #f2711c;margin-right: 0.5rem;"></i>
<span style="color: #888;font-size: 12px;">{{.i18n.Tr "cloudbrain.train_dataset_path_rule_1" | Safe}}</span>
</div>
</div>
<div class="required min_title inline field" style="margin-bottom: 0rem !important;">
@@ -104,7 +104,7 @@
</div>
<!--{{template "custom/select_model" .}} -->
<div>
<div class="select-multi-model" data-model-id="{{.model_id}}" data-model-name="{{.model_name}}" data-model-version="{{.model_version}}"
<div class="select-multi-model" data-model-id="{{.model_id}}" data-model-name="{{.model_name}}" data-model-version="{{.model_version}}" data-multiple="true"
data-pre-train-model-url="{{.pre_train_model_url}}" data-ckpt-name="{{.ckpt_name}}"></div>
<div id="select-multi-model"></div>
</div>
@@ -160,7 +160,7 @@
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label>
<select class="ui dropdown width48" id="__specs__" style='width:385px' name="spec_id" ovalue="{{.spec_id}}" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}></select>
<span><i class="question circle icon link" data-content="{{.i18n.Tr "repo.modelarts.train_job.resource_helper"}}" data-position="right center" data-variation="mini"></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>


+ 13
- 3
templates/repo/grampus/trainjob/show.tmpl View File

@@ -70,12 +70,22 @@
</span>
</div>
<div style="float: right;">
{{if and ($.canDownload) (ne .Status "WAITING") ($.Permission.CanWrite $.UnitTypeModelManage) (ne .ComputeResource "GCU")}}
{{if and ($.canDownload) (ne .Status "WAITING")}}
<a class="ti-action-menu-item" id="{{.VersionName}}-create-model"
onclick="showcreate({DisplayJobName:{{.DisplayJobName}},JobName:{{.JobName}},JobID:{{.JobID}},VersionName:{{.VersionName}},EngineName:{{.EngineName}},ComputeResource:{{.ComputeResource}},Type:{{.Type}}})">{{$.i18n.Tr "repo.modelarts.create_model"}}</a>
{{else}}
<a class="ti-action-menu-item disabled" id="{{.VersionName}}-create-model">{{$.i18n.Tr "repo.modelarts.create_model"}}</a>
{{end}}
{{if and ($.canDownload) (ne .Status "WAITING")}}
<a class="ti-action-menu-item export-dataset" style="position:relative" id="{{.VersionName}}-export-dataset" data-version="{{.VersionName}}" data-jobid="{{.JobID}}" data-repopath="/{{$.RepoRelPath}}/datasets/model">
{{$.i18n.Tr "repo.export_result_to_dataset"}}
<div class="export-popup" id="{{.VersionName}}-popup">
<div class="ui active centered inline loader" style="width: 100%;display: flex;align-items: center;">{{$.i18n.Tr "repo.loader_result_file"}}</div>
</div>
</a>
{{else}}
<a class="ti-action-menu-item disabled" id="{{.VersionName}}-export-dataset">{{$.i18n.Tr "repo.export_result_to_dataset"}}</a>
{{end}}
</div>
</span>
</span>
@@ -199,7 +209,7 @@
</td>

<td class="ti-text-form-content">
<div class="text-span text-span-w">{{if .CkptName}}{{.CkptName}}{{else}}--{{end}}</div>
<div class="text-span text-span-w" {{if .CkptName}}title="{{.CkptName}}"{{end}}>{{if .CkptName}}{{.CkptName}}{{else}}--{{end}}</div>
</td>
</tr>
</tbody>
@@ -568,7 +578,7 @@

</div>
</div>
{{template "custom/export_dataset" .}}
</div>
{{template "base/footer" .}}
<script type="text/javascript" src="/self/ztree/js/jquery.ztree.core.js?v={{MD5 AppVer}}"></script>


+ 2
- 2
templates/repo/modelarts/inferencejob/new.tmpl View File

@@ -144,7 +144,7 @@
</div>
-->
<div>
<div class="select-multi-model" data-required="true" data-model-id="{{.model_id}}" data-model-name="{{.model_name}}" data-model-version="{{.model_version}}"
<div class="select-multi-model" data-required="true" data-model-id="{{.model_id}}" data-model-name="{{.model_name}}" data-model-version="{{.model_version}}" data-multiple="true"
data-pre-train-model-url="{{.pre_train_model_url}}" data-ckpt-name="{{.ckpt_name}}"></div>
<div id="select-multi-model"></div>
</div>
@@ -281,7 +281,7 @@
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label>
<select class="ui dropdown width48" id="__specs__" name="spec_id" ovalue="{{.spec_id}}" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}></select>
<span><i class="question circle icon link" data-content="{{.i18n.Tr "repo.modelarts.train_job.resource_helper"}}" data-position="right center" data-variation="mini"></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>


+ 1
- 1
templates/repo/modelarts/notebook/new.tmpl View File

@@ -110,7 +110,7 @@
{{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}
placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' name="spec_id"></select>
<span><i class="question circle icon link" data-content="{{.i18n.Tr "repo.modelarts.train_job.resource_helper"}}" data-position="right center" data-variation="mini"></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip" style="padding:0 5px;margin:6px 0;margin-left:154px;font-size:12px;width:48.5%!important;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>


+ 1
- 1
templates/repo/modelarts/notebook/show.tmpl View File

@@ -205,7 +205,7 @@
</td>

<td class="ti-text-form-content">
<div class="text-span text-span-w">{{if .CkptName}}{{.CkptName}}{{else}}--{{end}}</div>
<div class="text-span text-span-w" {{if .CkptName}}title="{{.CkptName}}"{{end}}>{{if .CkptName}}{{.CkptName}}{{else}}--{{end}}</div>
</td>
</tr>
<tr class="ti-no-ng-animate">


+ 2
- 2
templates/repo/modelarts/trainjob/index.tmpl View File

@@ -178,12 +178,12 @@
<div class="ui compact buttons">
{{$.CsrfTokenHtml}}
{{if .CanModify}}
{{if and .CanModify (not .FineTune)}}
<a style="padding: 0.5rem 1rem;" class="ui basic blue button __btn_edit__" href="{{if eq .Cloudbrain.Type 1}}{{$.Link}}/{{.JobID}}{{else if eq .Cloudbrain.Type 0}}{{$.RepoLink}}/cloudbrain/train-job/{{.JobID}}{{else if eq .Cloudbrain.Type 2}}{{$.RepoLink}}/grampus/train-job/{{.JobID}}{{end}}/create_version{{if .VersionName}}?version_name={{.VersionName}}{{end}}">
{{$.i18n.Tr "repo.modelarts.modify"}}
</a>
{{else}}
<a class="ui basic disabled button">
<a class="ui basic disabled button" style="{{if .FineTune}}display:none;{{end}}">
{{$.i18n.Tr "repo.modelarts.modify"}}
</a>
{{end}}


+ 2
- 2
templates/repo/modelarts/trainjob/new.tmpl View File

@@ -104,7 +104,7 @@
<!--{{template "custom/select_model" .}} -->
<div>
<div class="select-multi-model" data-model-id="{{.model_id}}" data-model-name="{{.model_name}}" data-model-version="{{.model_version}}"
<div class="select-multi-model" data-model-id="{{.model_id}}" data-model-name="{{.model_name}}" data-model-version="{{.model_version}}" data-multiple="true"
data-pre-train-model-url="{{.pre_train_model_url}}" data-ckpt-name="{{.ckpt_name}}"></div>
<div id="select-multi-model"></div>
</div>
@@ -197,7 +197,7 @@
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label>
<select class="ui dropdown width48" id="__specs__" name="spec_id" ovalue="{{.spec_id}}" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}} style="color:red"></select>
<span><i class="question circle icon link" data-content="{{.i18n.Tr "repo.modelarts.train_job.resource_helper"}}" data-position="right center" data-variation="mini"></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>


+ 16
- 7
templates/repo/modelarts/trainjob/show.tmpl View File

@@ -39,7 +39,7 @@
<div class="active section">{{.displayJobName}}</div>
</div>
</h4>
{{range $k ,$v := .version_list_task}}
<div class="ui accordion border-according" id="accordion{{.VersionName}}"
data-repopath="{{$.RepoRelPath}}/modelarts/train-job" data-jobid="{{.JobID}}"
@@ -52,21 +52,29 @@
<span>
<div style="float: right;">
{{$.CsrfTokenHtml}}
{{if and (.CanModify) (ne .Status "WAITING") ($.Permission.CanWrite $.UnitTypeModelManage) }}
{{if and (.CanModify) (ne .Status "WAITING")}}
<a class="ti-action-menu-item" id="{{.VersionName}}-create-model"
onclick="showcreate({DisplayJobName:{{.DisplayJobName}},JobName:{{.JobName}},JobID:{{.JobID}},VersionName:{{.VersionName}},EngineID:{{.EngineID}},EngineName:{{.EngineName}}})">{{$.i18n.Tr "repo.modelarts.create_model"}}</a>
{{else}}
<a class="ti-action-menu-item disabled" id="{{.VersionName}}-create-model">{{$.i18n.Tr "repo.modelarts.create_model"}}</a>
{{end}}

{{if .CanModify}}
{{if and (.CanModify) (ne .Status "WAITING")}}
<a class="ti-action-menu-item export-dataset" style="position:relative" id="{{.VersionName}}-export-dataset" data-version="{{.VersionName}}" data-jobid="{{.JobID}}" data-repopath="/{{$.RepoRelPath}}/datasets/model">
{{$.i18n.Tr "repo.export_result_to_dataset"}}
<div class="export-popup" id="{{.VersionName}}-popup">
<div class="ui active centered inline loader" style="width: 100%;display: flex;align-items: center;">{{$.i18n.Tr "repo.loader_result_file"}}</div>
</div>
</a>
{{else}}
<a class="ti-action-menu-item disabled" id="{{.VersionName}}-export-dataset">{{$.i18n.Tr "repo.export_result_to_dataset"}}</a>
{{end}}
{{if and .CanModify (not .FineTune)}}
<a class="ti-action-menu-item"
href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}&path=show">{{$.i18n.Tr "repo.modelarts.modify"}}</a>
{{else}}
<a class="ti-action-menu-item disabled"
<a class="ti-action-menu-item disabled" style="{{if .FineTune}}display:none;{{end}}"
href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}">{{$.i18n.Tr "repo.modelarts.modify"}}</a>
{{end}}

{{if .CanDel}}
<a class="ti-action-menu-item stop-show-version {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED" "SUCCEEDED" "STOPPED"}}disabled {{end}}"
id="{{.VersionName}}-stop"
@@ -231,7 +239,7 @@
</td>

<td class="ti-text-form-content">
<div class="text-span text-span-w">{{if .CkptName}}{{.CkptName}}{{else}}--{{end}}</div>
<div class="text-span text-span-w" {{if .CkptName}}title="{{.CkptName}}"{{end}}>{{if .CkptName}}{{.CkptName}}{{else}}--{{end}}</div>
</td>
</tr>
</tbody>
@@ -569,6 +577,7 @@

</div>
</div>
{{template "custom/export_dataset" .}}
</div>
{{template "base/footer" .}}



+ 2
- 2
templates/repo/modelarts/trainjob/version_new.tmpl View File

@@ -127,7 +127,7 @@

<!--{{template "custom/select_model" .}} -->
<div>
<div class="select-multi-model" data-model-id="{{.model_id}}" data-model-name="{{.model_name}}" data-model-version="{{.model_version}}"
<div class="select-multi-model" data-model-id="{{.model_id}}" data-model-name="{{.model_name}}" data-model-version="{{.model_version}}" data-multiple="true"
data-pre-train-model-url="{{.pre_train_model_url}}" data-ckpt-name="{{.ckpt_name}}"></div>
<div id="select-multi-model"></div>
</div>
@@ -213,7 +213,7 @@
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label>
<select id="__specs__" class="ui dropdown width48" name="spec_id" ovalue="{{.spec_id}}" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}></select>
<span><i class="question circle icon link" data-content="{{.i18n.Tr "repo.modelarts.train_job.resource_helper"}}" data-position="right center" data-variation="mini"></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:156px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>


+ 1
- 1
templates/repo/modelsafety/new.tmpl View File

@@ -272,7 +272,7 @@
name="spec_id">
</select>
<span><i class="question circle icon link" data-content="{{.i18n.Tr "repo.modelarts.train_job.resource_helper"}}" data-position="right center" data-variation="mini"></i></span>
<a href="{{AppSubUrl}}/resource_desc" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
<a href="https://openi.pcl.ac.cn/docs/index.html#/quickstart/resources" target="_blank">{{.i18n.Tr "custom.resource_description"}}</a>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>


+ 1
- 1
templates/user/dashboard/cloudbrains.tmpl View File

@@ -253,7 +253,7 @@
</div>
{{end}}
<!-- 修改任务 -->
{{if eq .JobType "TRAIN"}}
{{if and (eq .JobType "TRAIN") (not .FineTune)}}
<div class="ui compact buttons __btn_edit__">
<a style="padding: 0.5rem 1rem;" class="ui basic blue button" href="{{AppSubUrl}}/{{.Repo.OwnerName}}/{{.Repo.Name}}{{if eq .Cloudbrain.Type 1}}/modelarts/train-job/{{.JobID}}{{else if eq .Cloudbrain.Type 0}}/cloudbrain/train-job/{{.JobID}}{{else if eq .Cloudbrain.Type 2}}/grampus/train-job/{{.JobID}}{{end}}/create_version{{if .VersionName}}?version_name={{.VersionName}}{{end}}">
{{$.i18n.Tr "repo.modelarts.modify"}}


+ 30
- 19
web_src/js/components/model/ModelSelect.vue View File

@@ -10,7 +10,7 @@
<div v-if="selectList.length" class="model-list-c" :class="errStatus ? 'error' : ''">
<div class="model-item" v-for="(item, index) in selectList" :key="item.id"
:title="item._modelName + '/' + item.name">
{{ item._modelName + '/' + item.name }}
{{ item._modelName + '/' + item.name }}{{ selectList.length > 1 ? ';' : '' }}
</div>
</div>
<input v-if="selectList.length == 0" type="text" class="disabled" style="width:100%"
@@ -74,37 +74,37 @@
</div>
</span>
<span class="model-repolink model-nowrap" @click.stop="return false;">
<a :href="'/' + data.repoOwnerName + '/' + data.repoName + '/modelmanage/model_readme_tmpl?name=' + data.name"
<a :href=" '/' + data.repoOwnerName + '/' + data.repoName + '/modelmanage/model_readme_tmpl?name=' + data.name "
target="_blank">
{{ data.repoOwnerName }}/{{ data.repoDisplayName }}
</a>
</span>
</span>
<span v-else style="display: flex">
<span class="model-nowrap" :title="node.label">
<span class="model-nowrap" :title=" node.label ">
{{ node.label }}
</span>
</span>
</span>
</el-tree>
<div class="pagination-c">
<el-pagination background @current-change="dlgPageChange" :current-page="dlgPage" :page-size="dlgPageSize"
layout="total, prev, pager, next" :total="dlgTotal">
<el-pagination background @current-change=" dlgPageChange " :current-page=" dlgPage "
:page-size=" dlgPageSize " layout="total, prev, pager, next" :total=" dlgTotal ">
</el-pagination>
</div>
</div>
<div class="right-area">
<div class="right-title"><span>{{ $t('modelObj.model_selected') }}</span></div>
<div class="right-selected-list">
<el-checkbox-group v-model="dlgSelectedModel">
<el-checkbox v-for="(item, index) in dlgSelectedModelList" :key="item.id" :label="item.id"
:true-label="item.id" :title="item.name" @change="(checked) => dlgChangeSelect(checked, item)">
<el-checkbox-group v-model=" dlgSelectedModel ">
<el-checkbox v-for="( item, index ) in dlgSelectedModelList " :key=" item.id " :label=" item.id "
:true-label=" item.id " :title=" item.name " @change=" (checked) => dlgChangeSelect(checked, item) ">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</div>
<div class="right-btn-c">
<el-button type="primary" size="small" @click="confirm">{{ $t('modelObj.model_ok') }}</el-button>
<el-button type="primary" size="small" @click=" confirm ">{{ $t('modelObj.model_ok') }}</el-button>
</div>
</div>
</div>
@@ -120,10 +120,10 @@ export default {
props: {
title: { type: String, default: '' },
showTitle: { type: Boolean, default: true },
maxCount: { type: Number, default: 1 },
},
data() {
return {
maxCount: 1,
required: false,
userName: location.pathname.split('/')[1],
repoName: location.pathname.split('/')[2],
@@ -381,21 +381,29 @@ export default {
const dataEl = this.$el.previousElementSibling;
if (dataEl) {
const dataset = dataEl.dataset;
const multiple = dataset.multiple;
if (multiple) {
this.maxCount = Infinity;
}
if (dataset.modelId) {
this.form_model_id = dataset.modelId;
this.form_model_name = dataset.modelName;
this.form_model_version = dataset.modelVersion;
this.form_pre_train_model_url = dataset.preTrainModelUrl;
this.form_ckpt_name = dataset.ckptName;
const file = {
_modelID: dataset.modelId,
_modelName: dataset.modelName,
_modelVersion: dataset.modelVersion,
_preTrainModelUrl: dataset.preTrainModelUrl,
name: dataset.ckptName,
};
file.id = `${file._modelID}|${file._modelName}|${file._modelVersion}|${file._preTrainModelUrl}|${file.name}`;
this.selectList.push(file);
const ckptNameList = dataset.ckptName.split(';');
for (let i = 0, iLen = ckptNameList.length; i < iLen; i++) {
const ckptName = ckptNameList[i];
const file = {
_modelID: dataset.modelId,
_modelName: dataset.modelName,
_modelVersion: dataset.modelVersion,
_preTrainModelUrl: dataset.preTrainModelUrl,
name: ckptName,
};
file.id = `${file._modelID}|${file._modelName}|${file._modelVersion}|${file._preTrainModelUrl}|${file.name}`;
this.selectList.push(file);
}
}
if (dataset.required) {
this.required = true;
@@ -450,6 +458,9 @@ export default {
line-height: 26px;
font-size: 14px;
color: rgba(0, 0, 0, 0.87);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.model-item-model {


+ 325
- 12
web_src/js/features/cloudbrainShow.js View File

@@ -21,11 +21,14 @@ export default async function initCloudrainSow() {
}
return str;
}

function escapeHTML(a){
a = "" + a;
return a.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");;
}
function timeFormat(date) {
return `${date.getFullYear()}-${paddingZeros(date.getMonth() + 1, 2)}-${paddingZeros(date.getDate(), 2)} ${paddingZeros(date.getHours(), 2)}:${paddingZeros(date.getMinutes(), 2)}:${paddingZeros(date.getSeconds(), 2)}`;
}
function logScroll(version_name, repoPath, ID, max = "", lines = 60) {
let container = document.querySelector(`#log${max}${version_name}`);
let scrollTop = container.scrollTop;
@@ -70,7 +73,7 @@ export default async function initCloudrainSow() {
$(`#log${version_name} input[name=end_line${max}]`).val(
data.EndLine
);
$(`#log${max}${version_name}`).append("<pre>" + data.Content);
$(`#log${max}${version_name}`).append("<pre>" + escapeHTML(data.Content));
}
}
}
@@ -114,7 +117,7 @@ export default async function initCloudrainSow() {
$(`#log${version_name} input[name=start_line${max}]`).val(
data.StartLine
); //如果变动就改变所对应的值
$(`#log${max}${version_name}`).prepend("<pre>" + data.Content);
$(`#log${max}${version_name}`).prepend("<pre>" + escapeHTML(data.Content));
}
}
).fail(function (err) {
@@ -176,7 +179,7 @@ export default async function initCloudrainSow() {
$(`#log${version_name} input[name=start_line${max}]`).val(
data.StartLine
);
$(`#log${max}${version_name}`).prepend("<pre>" + data.Content);
$(`#log${max}${version_name}`).prepend("<pre>" + escapeHTML(data.Content));
if (data.Lines == 0) {
if (max) {
$("body").toast({
@@ -218,7 +221,7 @@ export default async function initCloudrainSow() {
$(`#log${version_name} input[name=start_line${max}]`).val(
data.StartLine
);
$(`#log${max}${version_name}`).prepend("<pre>" + data.Content);
$(`#log${max}${version_name}`).prepend("<pre>" + escapeHTML(data.Content));
}
}
}
@@ -287,7 +290,7 @@ export default async function initCloudrainSow() {
$(`#log${version_name} input[name=start_line${max}]`).val(
data.StartLine
);
$(`#log${max}${version_name}`).append("<pre>" + (data.Content||''));
$(`#log${max}${version_name}`).append("<pre>" + (escapeHTML(data.Content)||''));
$.get(
`/api/v1/repos/${repoPath}/${ID}/log?version_name=${version_name}&base_line=${data.EndLine}&lines=${lines}&order=desc`,
(data) => {
@@ -320,7 +323,7 @@ export default async function initCloudrainSow() {
data.EndLine
);
}
$(`#log${max}${version_name}`).append("<pre>" + data.Content);
$(`#log${max}${version_name}`).append("<pre>" + escapeHTML(data.Content));
}
}
}
@@ -374,11 +377,10 @@ export default async function initCloudrainSow() {
let html = "";
if (jsonObj != null){
let podEventArray = jsonObj['JobEvents'];
console.log("podEventArray",podEventArray)
if(podEventArray != null){
for(var i=0; i < podEventArray.length;i++){
for(let i=0; i < podEventArray.length;i++){
if (podEventArray[i]["reason"] != "") {
let time = new Date(podEventArray[i]["timestamp"])
let time = podEventArray[i]["timestamp"]&&new Date(podEventArray[i]["timestamp"])
html += `<p><b>[${podEventArray[i]["reason"]}]</b> <span>${time.toLocaleString()}</span></p>`
html += `<p>${podEventArray[i]["message"]}</p>`;
}
@@ -472,7 +474,7 @@ export default async function initCloudrainSow() {
(data) => {
$("input[name=end_line]").val(data.EndLine);
$("input[name=start_line]").val(data.StartLine);
$(`#log_file${version_name}`).text(data.Content);
$(`#log_file${version_name}`).text(escapeHTML(data.Content));
document.getElementById("mask").style.display = "none";
}
).fail(function (err) {
@@ -531,6 +533,317 @@ export default async function initCloudrainSow() {

e.stopPropagation();
});
let initShowExportDataset = true
let initShowNoDataset = true
let canExportDataset = true
let fileList = []
let timer = null
let last_version = ''
let datasetID = ''
$('.ui.accordion .export-dataset').on('click', function (e) {
const version_name = this.dataset.version;
const jobId = this.dataset.jobid;
const repoPath = this.dataset.repopath;
const Fileurl = `${repoPath}/getmodelfile`
const dataUrl = `${repoPath}/getcurrentdataset`
const exportUrl = `${repoPath}/export_exist_dataset`
const getProgressUrl = `${repoPath}/getprogress`
if (!initShowExportDataset && last_version===version_name) {
if (initShowNoDataset) {
$(".ui.export_dataset.modal").modal({
onShow: function () {
$('.ui.dimmer').css({ "background-color": "rgb(136, 136, 136,0.7)" })
$(".ui.export_dataset.modal .cancel").on('click', function () {
$(".ui.export_dataset.modal").modal('hide')
})
},
onHide: function (params) {
$('.ui.modal.export_dataset #container').off("click")
$('#export-dataset-select').off("click")
$('#export-dataset-type').off("click")
$('.ui.export_dataset.modal .error.message').text('').hide()
},
onApprove: function () {
const modelFileSelectEle = $(".ui.export_dataset.modal #export-dataset-file .items").find('.file_item')
if (modelFileSelectEle.length !== 0 && canExportDataset) {
modelFileSelectEle.each(function (index) {
fileList.push($(this).attr('data-index'))
})
const type = Number($('.ui.modal.export_dataset input[name="type"]').val())
const csrf = $('.ui.modal.export_dataset input[name="_csrf"]').val()
const desc = $('.ui.modal.export_dataset textarea[name="description"]').val() //,description:desc
let params = { _csrf: csrf, jobId: jobId, versionName: version_name, datasetId: datasetID, modelSelectedFile: fileList.join(';'), type: type,description:desc }
postExportDataset(exportUrl, params, getProgressUrl)
} else {
$('.ui.export_dataset.modal .error.message').text(`${i18n['exportDataset']['please_select_file']}`).show()
}
return false;
},
}).modal('show').modal('setting', 'closable', false)
} else {
$(".ui.no_export_dataset.modal").modal({
onShow: function () {
$('.ui.dimmer').css({ "background-color": "rgb(136, 136, 136,0.7)" })
},
}).modal("show")
}
} else {
$(`.ui.accordion #${version_name}-export-dataset .export-popup`).show()
$('.ui.export_dataset.modal .error.message').text('').hide()
$.get(dataUrl, (data) => {
initShowExportDataset = false
last_version = version_name
if (data.code === 0) {
datasetID = data.dataset.ID
getModelFileList(Fileurl, version_name, jobId)
getInitEXportDataset(getProgressUrl,data.dataset.ID,jobId,version_name)
$(".ui.export_dataset.modal").modal({
onApprove: function () {
const modelFileSelectEle = $(".ui.export_dataset.modal #export-dataset-file .items").find('.file_item')
if (modelFileSelectEle.length !== 0 && canExportDataset) {
modelFileSelectEle.each(function (index) {
fileList.push($(this).attr('data-index'))
})
const type = Number($('.ui.modal.export_dataset input[name="type"]').val())
const csrf = $('.ui.modal.export_dataset input[name="_csrf"]').val()
const desc = $('.ui.modal.export_dataset textarea[name="description"]').val() //,description:desc
let params = { _csrf: csrf, jobId: jobId, versionName: version_name, datasetId: data.dataset.ID, modelSelectedFile: fileList.join(';'), type: type, description: desc }
postExportDataset(exportUrl, params, getProgressUrl)
} else {
$('.ui.export_dataset.modal .error.message').text(`${i18n['exportDataset']['please_select_file']}`).show()
}
return false;
},
onShow: function () {
$(`.ui.accordion #${version_name}-export-dataset .export-popup`).hide()
$('.ui.dimmer').css({ "background-color": "rgb(136, 136, 136,0.7)" })
},
onHide: function () {
$('.ui.modal.export_dataset #container').off("click")
$('#export-dataset-select').off("click")
$('#export-dataset-type').off("click")
$('.ui.export_dataset.modal .error.message').text('').hide()
}
})
.modal("show")
.modal('setting', 'closable', false)
} else {
$(".ui.no_export_dataset.modal").modal({
onShow: function () {
$(`.ui.accordion #${version_name}-export-dataset .export-popup`).hide()
$('.ui.dimmer').css({ "background-color": "rgb(136, 136, 136,0.7)" })
},
}).modal("show")
initShowNoDataset = false
}
})
}
$('#export-dataset-select').on('click', function () {
$(this).find('#model-file-wrap').show()
})
$('#model-file-export').on('click','.delete.icon', function () {
let fileEle = $(this).siblings('span').text()
$(this).parent().remove()
const $parentCheckbox = $('#model-file-result').find(`input[name="${fileEle}"]`).parent()
$parentCheckbox.checkbox('set unchecked')
})
$('.ui.modal.export_dataset #container').on('click', function (e) {
if ($(e.target).closest('#model-file-wrap').length === 0 && $(e.target).closest('#export-dataset-select').length !== 1) {
$(this).find('#model-file-wrap').hide()
}
})
$('#export-dataset-type').on('click', function (e,arg1) {
document.querySelectorAll('#export-dataset-type a').forEach((item) => {
item.classList.remove('active')
})
if (arg1) {
$('#export-dataset-type a')[arg1].classList.add('active')
document.querySelector('input[name="type"]').value=arg1
} else {
e.target.classList.add('active')
document.querySelector('input[name="type"]').value=e.target.dataset.type
}
})
e.stopPropagation();
})
function getInitEXportDataset(getProgressUrl, datasetId, jobId,version_name) {
let setIntervalFlag = false
$('.ui.modal.export_dataset #model-file-export').empty()
$.get(getProgressUrl, { progressId: `${datasetId}_${jobId}_${version_name}` }, (data) => {
const result = data && JSON.parse(data)
if (Object.keys(result).length > 0) {
canExportDataset = false
let fileInitList = Object.keys(result).filter((item) => item !== '##type##')
$($('#export-dataset-type')).trigger('click',`${result['##type##']}`)
fileInitList.forEach((item) => {
if (result[item] === -1) {
let itemHtml = `<div data-index="${item}" class="file_item">
<span class="nowrap" style="width:80%" title="${item}">${item}</span>
<div class="file_error"><i class="ri-close-circle-line failed"></i><span>${i18n['exportDataset']['export_failed']}</span></div>
</div>`
$('.ui.modal.export_dataset #model-file-export').append(itemHtml)
}
if (result[item] === -2) {
let itemHtml = `<div data-index="${item}" class="file_item">
<span class="nowrap" style="width:80%" title="${item}">${item}</span>
<div class="file_error"">
<i class="ri-close-circle-line failed"></i>
<span>${i18n['exportDataset']['export_failed']}</span>
<span data-tooltip="${i18n['exportDataset']['export_has_same_file']}" data-inverted="" data-variation="tiny">
<i class="ri-question-fill question"></i>
</span>
</div>
</div>`
$('.ui.modal.export_dataset #model-file-export').append(itemHtml)
}
if (result[item] === 100) {
let itemHtml = `<div data-index="${item}" class="file_item">
<span class="nowrap" style="width:80%" title="${item}">${item}</span>
<div class="file_success"">
<i class="ri-checkbox-circle-line success" style="vertical-align: middle;"></i>
<span>${i18n['exportDataset']['export_success']}</span>
</div>
</div>`
$('.ui.modal.export_dataset #model-file-export').append(itemHtml)
}
if (result[item] === 0) {
let itemHtml = `<div data-index="${item}" class="file_item">
<span class="nowrap" style="width:80%" title="${item}">${item}</span>
<div class="file_wait""><i class="ri-loader-2-line waiting spin"></i><span>${i18n['exportDataset']['exporting']}</span></div>
</div>`
$('.ui.modal.export_dataset #model-file-export').append(itemHtml)
setIntervalFlag = true
}
})
if (setIntervalFlag) {
timer && clearInterval(timer)
timer = setIntervalImmediately(getProgress, 5000, getProgressUrl, `${datasetId}_${jobId}`)
}
}
})
}
function getModelFileList(Fileurl,version_name,jobId) {
$.get(Fileurl, { versionName: version_name, jobId: jobId }, (data) => {
$('.ui.modal.export_dataset #model-file-result').empty()
let html = ''
if (data.code === 0 && data.files.length !== 0) {
let dataFileList = data.files.sort((a, b) => {
return a.FileName.localeCompare(b.FileName)
})
dataFileList.forEach(element => {
html += `<div class="item">
<div class="ui child checkbox">
<input type="checkbox" name="${element.FileName}">
<label>${element.FileName}</label>
</div>
</div>`
});
$('.ui.modal.export_dataset #model-file-result').append(html)
$('#model-file-result.list .child.checkbox').checkbox({
onChecked: function () {
$('.ui.export_dataset.modal .error.message').text('').hide()
if (!canExportDataset) {
$('.ui.modal.export_dataset #model-file-export').empty()
canExportDataset = true
}
let itemHtml = ''
let fileName = $(this).attr('name')
itemHtml += `<div data-index="${fileName}" class="file_item">
<span class="nowrap" style="width:80%" title="${fileName}">${fileName}</span>
<i class="delete icon" style="cursor:pointer"></i>
</div>`
$('.ui.modal.export_dataset #model-file-export').append(itemHtml)
},
onUnchecked: function() {
let fileName = $(this).attr('name')
$('.ui.modal.export_dataset #model-file-export').find(`div[data-index="${fileName}"]`).remove()

},
})
}
}).fail(function (err) {
console.log(err);
});
}
function getProgress(getProgressUrl,progressId) {
const $statusEle = $('#model-file-export .file_item').find('div')
let fileLength = $statusEle.length
let count = 0
$.get(getProgressUrl, { progressId: progressId }, (data) => {
const result = data && JSON.parse(data)
let sortResult = Object.keys(result).sort((a,b) => {
return fileList.indexOf(a)-fileList.indexOf(b)
})
let filterResult = sortResult.filter((item) => item !== '##type##')
filterResult.forEach((item, index) => {
console.log(item)
if (result[item] === -1) {

if (!$($statusEle[index]).hasClass('file_error')) {
$($statusEle[index]).replaceWith(`<div class="file_error"><i class="ri-close-circle-line failed"></i><span>${i18n['exportDataset']['export_failed']}</span></div>`)
}
count++
}
if (result[item] === -2) {
if (!$($statusEle[index]).hasClass('file_error')) {
$($statusEle[index]).replaceWith(`<div class="file_error""><i class="ri-close-circle-line failed"></i><span>${i18n['exportDataset']['export_failed']}</span><span data-tooltip="${i18n['exportDataset']['export_has_same_file']}" data-inverted="" data-variation="tiny"><i class="ri-question-fill question"></i></span></div>`)
}
count++
}
if (result[item] === 100 ) {
if (!$($statusEle[index]).hasClass('file_success')) {
$($statusEle[index]).replaceWith(`<div class="file_success""><i class="ri-checkbox-circle-line success" style="vertical-align: middle;"></i><span>${i18n['exportDataset']['export_success']}</span></div>`)
}
count++
}
console.log("count:",count)
if (count === fileLength) {
$(".ui.export_dataset.modal").modal('refresh')
timer && clearInterval(timer)
$('#export-dataset-select').on('click', function () {
$(this).find('#model-file-wrap').show()
})
$('.ui.modal.export_dataset .ui.approve').removeClass('disabled')
fileList = []
canExportDataset = false
$('#model-file-result.list .child.checkbox.checked').each(function () {
$(this).checkbox('set unchecked')
})
return
}
})
})
}
function postExportDataset(url, params, getProgressUrl) {
$.post(url, params, (data) => {
if (data.code === '0') {
$('#model-file-export .delete.icon').each(function () {
$(this).replaceWith(`<div class="file_wait""><i class="ri-loader-2-line waiting spin"></i><span>${i18n['exportDataset']['exporting']}</span></div>`)
})
$('.ui.modal.export_dataset .ui.approve').addClass('disabled')
$('#export-dataset-select').off("click")
// $('.ui.modal.export_dataset').off("click")
getProgress(getProgressUrl,data.progressId)
timer && clearInterval(timer)
timer = setIntervalImmediately(getProgress,5000,getProgressUrl,data.progressId)
}
})
}
function setIntervalImmediately(func, interval,...args) {
func(...args)
return setInterval(func,interval,...args)
};
// $('.ui.pointing.secondary.menu .item:eq(0)').click(function(e) {
// const self = $(this);
// setTimeout(function() {


+ 8
- 0
web_src/js/features/cloudrbanin.js View File

@@ -294,6 +294,14 @@ export default async function initCloudrain() {
$.post(url, { version_name: versionName }, (data) => {
if (data.StatusOK === 0 || data.Code === 0) {
location.reload();
} else {
$(".alert")
.html(data.Message)
.removeClass("alert-success")
.addClass("alert-danger")
.show()
.delay(1500)
.fadeOut();
}
}).fail(function (err) {
console.log(err);


+ 15
- 1
web_src/js/features/i18nVue.js View File

@@ -152,9 +152,16 @@ export const i18nVue = {
model_selected: '已选模型文件',
model_ok: '确定',
model_most: '最多不超过 {msg} 个文件',
model_should_same_model: '选择同一模型下的文件',
model_should_same_model: '选择同一模型下的文件',
model_suport_file_tips: '模型文件支持的格式为 [ckpt, pb, h5, json, pkl, pth, t7, pdparams, onnx, pbtxt, keras, mlmodel, cfg, pt]',
},
exportDataset: {
export_failed: '导出失败',
export_has_same_file: '当前项目已存在相同的数据集文件',
export_success: '导出成功',
exporting: '正在导出',
please_select_file:'请先选择文件',
}
},
US: {
@@ -316,5 +323,12 @@ export const i18nVue = {
model_should_same_model: 'Select the files should in the same model.',
model_suport_file_tips: 'The supported format of the model file is [ckpt, pb, h5, json, pkl, pth, t7, pdparams, onnx, pbtxt, keras, mlmodel, cfg, pt]',
},
exportDataset: {
export_failed: 'Export failed',
export_has_same_file:'The same dataset file already exists in the current project',
export_success: 'Export success',
exporting: ' Exporting',
please_select_file: 'Please select a file first',
}
},
};

+ 2
- 2
web_src/js/features/images.js View File

@@ -14,7 +14,7 @@ export default async function initImage(){
identifier : 'tag',
rules: [
{
type: 'regExp[/^[A-Za-z0-9_.-]{1,100}[A-Za-z0-9_.]$/]',
type: 'regExp[/^[a-zA-Z][\\w|\\-|\\.]*$/]',
}
]
},
@@ -115,7 +115,7 @@ export default async function initImage(){
document.querySelector('input[name="type"]').value=e.target.dataset.type
})
$('.ui.create_image.green.button').click(()=>{
let pattenTag = new RegExp(/^[A-Za-z0-9_.-]{1,100}[A-Za-z0-9_.]$/)
let pattenTag = new RegExp(/^[a-zA-Z][\w|\-|\.]*$/)
if(!pattenTag.test($("input[name='tag']").val())){
$("input[name='tag']").parent().addClass('error')
return false


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

Loading…
Cancel
Save