#4162 finetune02-盘古大模型微调功能

Merged
zouap merged 41 commits from finetune02 into V20230517 1 year ago
  1. +27
    -1
      models/cloudbrain.go
  2. +8
    -0
      models/error.go
  3. +238
    -0
      models/modelarts_deploy.go
  4. +58
    -0
      models/modelarts_deploy_queue.go
  5. +2
    -0
      models/models.go
  6. +45
    -0
      modules/auth/wechat/finetune.go
  7. +4
    -2
      modules/auth/wechat/template.go
  8. +3
    -1
      modules/cloudbrain/cloudbrain.go
  9. +17
    -0
      modules/convert/cloudbrain.go
  10. +23
    -0
      modules/convert/finetune.go
  11. +26
    -0
      modules/cron/tasks_basic.go
  12. +143
    -0
      modules/modelarts/modelarts.go
  13. +347
    -0
      modules/modelarts/resty.go
  14. +1
    -0
      modules/notification/base/notifier.go
  15. +4
    -0
      modules/notification/base/null.go
  16. +7
    -0
      modules/notification/notification.go
  17. +6
    -0
      modules/notification/wechat/wechat.go
  18. +104
    -0
      modules/setting/finetune.go
  19. +1
    -0
      modules/setting/setting.go
  20. +60
    -18
      modules/structs/cloudbrain.go
  21. +13
    -0
      modules/structs/finetune.go
  22. +13
    -0
      options/locale/locale_en-US.ini
  23. +12
    -0
      options/locale/locale_zh-CN.ini
  24. +65
    -45
      routers/api/v1/api.go
  25. +249
    -0
      routers/api/v1/finetune/finetune.go
  26. +274
    -0
      routers/api/v1/finetune/panguervice.go
  27. +22
    -0
      routers/api/v1/user/cloudbrain.go
  28. +31
    -0
      routers/modelapp/pangu.go
  29. +11
    -7
      routers/repo/cloudbrain.go
  30. +2
    -2
      routers/repo/grampus.go
  31. +1
    -1
      routers/repo/modelarts.go
  32. +12
    -0
      routers/repo/setting.go
  33. +10
    -0
      routers/routes/routes.go
  34. +15
    -7
      services/cloudbrain/cloudbrainTask/train.go
  35. +1
    -1
      templates/admin/cloudbrain/list.tmpl
  36. +1
    -1
      templates/base/head.tmpl
  37. +1
    -1
      templates/base/head_course.tmpl
  38. +1
    -1
      templates/base/head_fluid.tmpl
  39. +1
    -1
      templates/base/head_home.tmpl
  40. +4
    -2
      templates/base/head_navbar.tmpl
  41. +4
    -2
      templates/base/head_navbar_fluid.tmpl
  42. +4
    -2
      templates/base/head_navbar_home.tmpl
  43. +4
    -2
      templates/base/head_navbar_pro.tmpl
  44. +5
    -0
      templates/model/base/index.tmpl
  45. +6
    -0
      templates/model/base/panguCreate.tmpl
  46. +5
    -0
      templates/model/base/panguIndex.tmpl
  47. +5
    -0
      templates/model/base/panguInference.tmpl
  48. +2
    -2
      templates/repo/modelarts/trainjob/index.tmpl
  49. +2
    -2
      templates/repo/modelarts/trainjob/show.tmpl
  50. +1
    -1
      templates/user/dashboard/cloudbrains.tmpl
  51. +1
    -1
      web_src/js/features/notification.js
  52. +10
    -0
      web_src/vuepages/apis/modules/common.js
  53. +138
    -0
      web_src/vuepages/apis/modules/modelbase.js
  54. +469
    -0
      web_src/vuepages/pages/modelbase/components/DatasetFileUploader.vue
  55. +210
    -0
      web_src/vuepages/pages/modelbase/components/ModelBaseDatasetSelect.vue
  56. +181
    -0
      web_src/vuepages/pages/modelbase/components/TopHeader.vue
  57. +583
    -0
      web_src/vuepages/pages/modelbase/components/cloudbrain/DatasetSelect.vue
  58. +136
    -0
      web_src/vuepages/pages/modelbase/components/cloudbrain/LoadingMask.vue
  59. +312
    -0
      web_src/vuepages/pages/modelbase/components/cloudbrain/ResourceSpecification.vue
  60. +227
    -0
      web_src/vuepages/pages/modelbase/components/cloudbrain/RunParameters.vue
  61. +127
    -0
      web_src/vuepages/pages/modelbase/components/cloudbrain/TaskName.vue
  62. +0
    -0
      web_src/vuepages/pages/modelbase/const.js
  63. +419
    -0
      web_src/vuepages/pages/modelbase/create/index.vue
  64. +17
    -0
      web_src/vuepages/pages/modelbase/create/vp-modelbase-create.js
  65. +448
    -0
      web_src/vuepages/pages/modelbase/home/index.vue
  66. +17
    -0
      web_src/vuepages/pages/modelbase/home/vp-modelbase-home.js
  67. +265
    -0
      web_src/vuepages/pages/modelbase/inference/index.vue
  68. +17
    -0
      web_src/vuepages/pages/modelbase/inference/vp-modelbase-inference.js
  69. +510
    -0
      web_src/vuepages/pages/modelbase/model/index.vue
  70. +17
    -0
      web_src/vuepages/pages/modelbase/model/vp-modelbase-model.js

+ 27
- 1
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:"-"`
}

@@ -3119,6 +3132,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)
}

+ 238
- 0
models/modelarts_deploy.go View File

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

import (
"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
}

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"
default:
statusConvert = "FAILED"
}
return statusConvert
}
}

+ 58
- 0
models/modelarts_deploy_queue.go View File

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

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

type ModelartsDeployQueue struct {
JobID string `xorm:"pk 'job_id'"`
ModelID string
ModelName string
ServiceID 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{})
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,


+ 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,


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

@@ -0,0 +1,23 @@
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,
}
}

+ 26
- 0
modules/cron/tasks_basic.go View File

@@ -5,6 +5,7 @@
package cron

import (
"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"
"context"
@@ -337,6 +338,28 @@ func registerHandleCloudbrainDurationStatistic() {
})
}

func registerSyncFinetunePanguDeployStatus() {
RegisterTaskFatal("sync_pangu_deploy_status", &BaseConfig{
Enabled: true,
RunAtStart: false,
Schedule: "@every 30s",
}, 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 30s",
}, func(ctx context.Context, _ *models.User, _ Config) error {
finetune.PanguServiceCreateQueue()
return nil
})
}

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

registerSyncFinetunePanguDeployStatus()
registerFinetunePanguServiceCreateQueue()
}

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

@@ -94,6 +94,9 @@ type GenerateTrainJobReq struct {
ModelId string
ModelVersion string
PreTrainModelUrl string
FineTune bool
FineTuneModelType int
FineTuneCategory int
}

type GenerateInferenceJobReq struct {
@@ -132,6 +135,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 +396,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 +1252,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
}

+ 347
- 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, fmt.Errorf("delete train job [%s] failed [%v]", jobID, err.Error())
}
}

retry := 0

sendjob:
@@ -1524,3 +1537,337 @@ 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" {
err := DeleteDeployService(deploy.ServiceID)
if err != nil {
log.Error("盘古微调部署: Delete DeployService API failed:%s %v", jobID, err.Error())
return err
}
err = DeleteDeployModel(deploy.ModelID)
if err != nil {
log.Error("盘古微调部署: Delete DeployModel API failed:%s %v", jobID, err.Error())
return err
}

err = models.DeleteModelartsDeploy(jobID)
if err != nil {
log.Error("盘古微调部署: Delete ModelartsDeploy from DB failed:%s %v", jobID, err.Error())
return err
}
} else {
log.Error("the job(%s) is a deploying finetune job, can be not deleted", jobID)
return fmt.Errorf("the job(%s) is a deploying finetune job, can be not deleted", jobID)
}
}
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)
}

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

@@ -0,0 +1,104 @@
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"`
}
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"`
}

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

@@ -0,0 +1,13 @@
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"`
}

+ 13
- 0
options/locale/locale_en-US.ini View File

@@ -1066,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.
@@ -3361,6 +3362,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
@@ -3422,3 +3426,12 @@ 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_service = There is running deployment service, cannot delete repository
stop_service_failed = Failed to stop deploy service

+ 12
- 0
options/locale/locale_zh-CN.ini View File

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

model_manager = 模型
model_base = 大模型基地
model_square = 模型广场
model_experience = 模型体验
model_noright=您没有操作权限。
@@ -3382,6 +3383,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 = 积分
@@ -3445,3 +3449,11 @@ system_error = 当前服务不可用,请稍后再试
insufficient_permission = 权限不足
param_error = 提交的参数有误

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

+ 65
- 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,9 @@ 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)
}, reqToken())

m.Group("/reward_point", func() {
m.Get("/is_admin", user.IsRewardPointAdmin)
@@ -798,6 +804,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 +1120,19 @@ func RegisterRoutes(m *macaron.Macaron) {
Delete(reqToken(), repo.DeleteTopic)
}, reqAdmin())
}, reqAnyRepoReader())
m.Group("/finetune", func() {
m.Get("/spec", finetune.GetSpec)
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,
})
}

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

@@ -0,0 +1,274 @@
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("盘古微调部署: Modelart ModelartsDeploy: Failed to copy %s to /model (%v)", panguCodePath, err)
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("deployment.code_copy_failed")))
return err
}
//log.Info("盘古微调部署: 拷贝推理代码文件成功, 源路径: %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("盘古微调部署: Modelart ModelartsDeploy: Failed to copy %s to /model (%v)", trainModel, err)
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("deployment.model_copy_failed")))
return err
}
//log.Info("盘古微调部署: 拷贝模型成功, 源路径: %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("盘古微调部署: 微调任务 [%s] AI应用 API调用失败:%v", jobID, err.Error())
ctx.JSON(http.StatusOK, models.BaseErrorMessageApi(ctx.Tr("deployment.builidng_fail")))
return err
}
log.Info("盘古微调部署: 微调任务 [%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("盘古微调部署: 当前无部署任务,可用运行资源 %v 卡。", cntQueue)
} else {
cntQueue = setting.FineTune.Pangu.Deploy.MaxDeployNum - len(runningDeploys)
log.Info("盘古微调部署: 当前正在运行 %v 部署任务,可用运行资源 %v 卡。", len(runningDeploys), cntQueue)
if cntQueue == 0 {
log.Info("盘古微调部署: 资源已满,停止当前定时任务。")
return
}
}

deployQueues, _ := models.GetModelartsDeployQueue(cntQueue)
if deployQueues == nil {
log.Error("盘古微调部署: 没有排队的部署任务,停止当前定时任务。")
return
}

for _, deploy := range deployQueues {
if deploy.ServiceID != "" {
//服务重启
err := modelarts.UpdateDeployService(deploy.ServiceID, models.UpdateDeployServiceParams{
Status: "running",
})
if err != nil {
log.Error("盘古微调部署: 微调任务 [%s],部署服务重启失败, 服务ID %s", deploy.JobID, deploy.ServiceID)
models.DeleteModelartsDeployQueueByJobID(deploy.JobID)
continue
}
} else {
//初次部署服务
log.Info("盘古微调部署: 部署服务开始创建", deploy.JobID)
serviceReq := &modelarts.GenerateDeployServiceReq{
JobID: deploy.JobID,
InferType: PanguDeployType,
ServiceName: deploy.ModelName + "-service",
Spec: PanguDeploySpec,
Duration: setting.FineTune.Pangu.Deploy.Duration + setting.FineTune.Pangu.Deploy.WarmupDuration,
TimeUnit: setting.FineTune.Pangu.Deploy.TimeUnit,
ScheduleType: PanguDeployScheduleType,
ModelID: deploy.ModelID,
InstanceCount: PanguDeployInstanceCnt,
}
serviceID, err := modelarts.GenerateDeployService(serviceReq)
if err != nil {
log.Error("盘古微调部署: 微调任务 [%s],部署服务重启失败, 服务ID %s", deploy.JobID, serviceID)
models.DeleteModelartsDeployQueueByJobID(deploy.JobID)
continue
}
}
log.Info("盘古微调部署: 微调任务 [%s],部署服务重启成功, 服务ID %s", deploy.JobID, deploy.ServiceID)
models.DeleteModelartsDeployQueueByJobID(deploy.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("盘古微调部署:此任务正在等待部署服务,不更新状态")
continue
} else if deploy.ServiceID == "" {
// 查模型
deployNew, err := modelarts.GetDeployModel(deploy.ModelID)
if err != nil {
log.Error("Get DeployModel API failed:%v", err.Error())
return
}
statusNew = models.DeployStatusConvert(deployNew.ModelStatus)
deploy.Status = statusNew
deploy.ModelStatus = deployNew.ModelStatus
if statusNew == "WAITING" {
models.CreateModelartsDeployQueue(&models.ModelartsDeployQueue{
JobID: deploy.JobID,
ModelID: deploy.ModelID,
ModelName: deploy.ModelName,
CreateUnix: timeutil.TimeStampNow(),
})
log.Info("盘古微调部署:创建模型成功,插入 modelarts_deploy_queue")
}
} else {
// 查服务
deployNew, err := modelarts.GetDeployService(deploy.ServiceID)
if err != nil {
log.Error("Get DeployService API failed:%v", err.Error())
return
}
statusNew = models.DeployStatusConvert(deployNew.Status)
deploy.ServiceStatus = deployNew.Status
// 部署成功后,等待30分钟再允许用户打开推理界面
if statusNew == "SUCCEEDED" {
currentTime := timeutil.TimeStampNow()
log.Info("盘古微调部署: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("盘古微调部署:部署成功,预热开始,预计完成时间:%v", deploy.CompleteUnix)
continue
} else if deploy.CompleteUnix >= currentTime {
log.Info("盘古微调部署:预热中,预计完成时间:%v", deploy.CompleteUnix)
continue
}
}
statusOld := deploy.Status
deploy.Status = statusNew
deploy.InferAddr = deployNew.InferAddr
if statusNew == "SUCCEEDED" && statusOld == "DEPLOYING" {
notification.NotifyChangeFinetuneStatus(deploy)
log.Info("盘古微调部署:部署服务成功,发送微信通知")
}
}
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("盘古微调部署: Get ModelartsDeploy from DB failed:%v", err.Error())
return
}

//serviceID := deploy.ServiceID
inferAddr := deploy.InferAddr
inferResults, err := modelarts.SendInferenceDeploy(inferAddr, option.Text)
if err != nil {
log.Error("盘古微调部署: SendInferenceDeploy API failed:%v", err.Error())
ctx.JSON(http.StatusOK, map[string]interface{}{
"code": 1,
"text": err.Error(),
})
return
}
log.Info("盘古微调部署: 推理结果返回成功, %s", 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("盘古微调部署: 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("盘古微调部署: 每个用户最多只能同时部署%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("盘古微调部署: 重启服务开始")
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("盘古微调部署: 停止服务")
deploy.Status = "STOP"
err := modelarts.UpdateDeployService(deploy.ServiceID, models.UpdateDeployServiceParams{
Status: option.Status,
})
if err != nil {
log.Error("盘古微调部署: Update DeployService API failed:%v", 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,
})
}

+ 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
- 7
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)
}
@@ -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
}
}
}



+ 2
- 2
routers/repo/grampus.go View File

@@ -1478,7 +1478,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 +1626,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)



+ 1
- 1
routers/repo/modelarts.go View File

@@ -1994,7 +1994,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)
}



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

@@ -511,6 +511,18 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success"))
ctx.Redirect(ctx.Repo.Owner.DashboardLink())
}
deployments, err := models.GetRunningServiceByUser(ctx.User.ID)
log.Info("盘古部署删除项目, %v", ctx.User.ID)
if err != nil {
ctx.ServerError("GetRunningServiceByUser", err)
return
} else if deployments != nil {
if len(deployments) > 0 {
ctx.Flash.Error(ctx.Tr("deployment.deletion_notice_service"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
return
}
}

case "delete-wiki":
if !ctx.Repo.IsOwner() {


+ 10
- 0
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() {


+ 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))


+ 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/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>


+ 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/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/show.tmpl View File

@@ -68,11 +68,11 @@
{{else}}
<a class="ti-action-menu-item disabled" id="{{.VersionName}}-export-dataset">{{$.i18n.Tr "repo.export_result_to_dataset"}}</a>
{{end}}
{{if .CanModify}}
{{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}}


+ 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"}}


+ 1
- 1
web_src/js/features/notification.js View File

@@ -52,7 +52,7 @@ export function initNotificationCount() {
return;
}
const notice = $(`
<div class="__notice-wrap" style="display:none;">
<div class="__notice-wrap" style="display:none;z-index:99;">
<style>
.__notice-container {
position: fixed;


+ 10
- 0
web_src/vuepages/apis/modules/common.js View File

@@ -10,3 +10,13 @@ export const getPromoteData = (filePathName) => {
},
});
}

// 获取个人积分信息
// return {pointAccount:{id,account_code,balance,total_earned,total_consumed,status,version,created_unix,updated_unix },cloudBrainPaySwitch}
export const getPointAccountInfo = () => {
return service({
url: '/api/v1/user/point_account',
method: 'get',
params: {},
});
}

+ 138
- 0
web_src/vuepages/apis/modules/modelbase.js View File

@@ -0,0 +1,138 @@
import service from '../service';

// 检验openi-notebook是否存在,如果不存在先创建openi-notebook仓库,再返回仓库存在
export const getCheckRepo = () => {
return service({
url: `/api/v1/finetune/checkRepo`,
method: 'get',
params: {},
});
}

// 获取大模型微调可用规格
export const getSpecInfo = (params) => {
return service({
url: `/api/v1/repos/${params.userName}/openi-notebook/finetune/spec`,
method: 'get',
params: {},
});
}

// 创建大模型微调训练任务
export const setFinetuneCreate = (data) => {
return service({
url: `/api/v1/repos/${data.userName}/openi-notebook/finetune/create`,
method: 'post',
params: {},
data: {
type: data.type, // 0为盘古2.6模型
display_job_name: data.display_job_name,
description: data.description,
attachment: data.attachment,
dataset_name: data.dataset_name,
sample_dataset_type: data.sample_dataset_type,
spec_id: data.spec_id,
run_para_list: data.run_para_list,
},
});
}

// 查询训练任务状态
export const getJobStatus = (params) => {
return service({
url: `/api/v1/repos/${params.userName}/openi-notebook/modelarts/train-job/${params.jobId}`,
method: 'get',
params: {},
});
}

// 停止训练任务
export const stopTrainJob = (params) => {
return service({
url: `/api/v1/repos/${params.userName}/openi-notebook/modelarts/train-job/${params.jobId}/stop_version`,
method: 'post',
params: {},
});
}

// 删除训练任务
export const deleteTrainJob = (params) => {
return service({
url: `/api/v1/repos/${params.userName}/openi-notebook/modelarts/train-job/${params.jobId}/del_version`,
method: 'post',
params: {},
});
}

// 查询微调任务列表
export const getFinetuneList = (params) => {
return service({
url: `/api/v1/repos/${params.userName}/openi-notebook/finetune`,
method: 'get',
params: {},
});
}

// 新建ModelArt部署
export const setFinetuneService = (data) => {
return service({
url: `/api/v1/repos/${data.userName}/openi-notebook/finetune/deploy/create`,
method: 'post',
params: {},
data: {
type: data.type, // 0为盘古2.6模型
job_id: data.jobId,
sample_dataset_type: data.sample_dataset_type, // 1文本分类, 2中英翻译, 3开放问答, 0自定义
},
});
}

// 查询部署状态
// params job_id
// return job_id, DEPLOYING部署中, SUCCEEDED部署成功, STOP停止, FAIL部署失败
export const getFinetuneServiceStatus = (params) => {
return service({
url: `/api/v1/repos/${params.userName}/openi-notebook/finetune/deploy/${params.jobId}`,
method: 'get',
params: {},
});
}

// 更新部署状态
// return { code-0成功,1失败 }
export const updateFinetuneService = (data) => {
return service({
url: `/api/v1/repos/${data.userName}/openi-notebook/finetune/deploy/update`,
method: 'post',
params: {},
data: {
job_id: data.jobId,
status: data.status, // 停止或重启部署服务,分别对应stopped或running
}
});
}

// 删除部署
// return { code-0成功,1失败 }
export const deleteFinetuneService = (data) => {
return service({
url: `/api/v1/repos/${data.userName}/openi-notebook/finetune/deploy/delete`,
method: 'post',
params: {},
data: { job_id: data.jobId, }
});
}

// 推理体验
// params jobId, text,
export const getFinetuneServiceInference = (data) => {
return service({
url: `/api/v1/repos/${data.userName}/openi-notebook/finetune/deploy/inference`,
method: 'post',
params: {},
data: {
job_id: data.jobId,
text: data.text,
}
});
}

+ 469
- 0
web_src/vuepages/pages/modelbase/components/DatasetFileUploader.vue View File

@@ -0,0 +1,469 @@
<template>
<div class="dataset-file-uploader">
<div class="content">
<input type="file" ref="fileInputRef" style="display:none;" accept=".zip" @change="fileChange">
<div class="file-list-c" :class="errStatus ? 'error' : ''">
<div class="file-item" v-for="(item, index) in selectList" :key="item.index">
{{ item.name }};
</div>
<div v-if="selectList.length == 0" class="file-item-placeholder">{{ '请选择本地数据集文件' }}</div>
</div>
<div class="btn-select" @click="selectClick">
<i class="el-icon-plus"></i>
<span>选择本地数据集文件</span>
</div>
</div>
</div>
</template>

<script>

import SparkMD5 from "spark-md5";
import { getChunks, getNewMultipart, getMultipartUrl, setCompleteMultipart } from '~/apis/modules/dataset';

const uploadChunkSize = 1024 * 1024 * 64;
const md5ChunkSize = 1024 * 1024 * 64;
const calcMd5ChunkSize = 1024 * 1024 * 1;
const maxFileSize = 20;

export default {
name: "DatasetFileUploader",
props: {
type: { type: Number, default: 0 },
datasetId: { type: String, default: '' },
},
data() {
return {
selectList: [],

uploading: false,
uploadFiles: [],
uploadLength: 0,
uploadSuccessLength: 0,
uploadStatusList: [],

resolveHandler: null,
rejectHandler: null,

errStatus: false,
};
},
watch: {},
methods: {
fileChange(evt) {
const files = evt.target.files || [];
this.selectList = [...files];
if (this.selectList.length) {
this.errStatus = false;
}
},
selectClick() {
this.$refs.fileInputRef.value = '';
this.$refs.fileInputRef.click();
},
calcFileMd5(file) {
const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
const chunkSize = md5ChunkSize;
const chunks = Math.ceil(file.size / chunkSize);
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
file.totalChunkCounts = chunks;
if (file.size == 0) {
file.totalChunkCounts = 1;
}
let currentChunk = 0;
this.uploadStatusList.push({
uploadUuid: file.__uuid,
name: file.name,
status: this.$t('modelManage.calcFileMd5'),
progress: 0,
infoCode: 3,
});
return new Promise((resolve, reject) => {
fileReader.onload = function (e) {
spark.append(e.target.result);
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
const md5 = spark.end();
spark.destroy();
file.uniqueIdentifier = md5;
resolve(md5);
}
};
fileReader.onerror = function (e) {
console.warn(file.name + ': calcFileMd5 went wrong.');
reject(e);
};

function loadNext() {
const start = currentChunk * chunkSize;
const end = ((start + calcMd5ChunkSize) >= file.size) ? file.size : start + calcMd5ChunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}
loadNext();
});
},
getChunksInfo(file) {
return getChunks({
md5: file.uniqueIdentifier,
type: this.type,
dataset_id: this.datasetId,
file_name: file.name
}).then(res => {
const data = res.data;
file.uploadID = data.uploadID;
file.uuid = data.uuid;
file.uploaded = data.uploaded;
file.chunks = data.chunks;
file.attachID = data.attachID;
file._datasetID = data.datasetID;
file._datasetName = data.datasetName;
file._fileName = data.fileName;
return file;
}).catch(err => {
console.info('getChunksInfo', err);
return err;
});
},
newUpload(file) {
return getNewMultipart({
totalChunkCounts: file.totalChunkCounts,
md5: file.uniqueIdentifier,
size: file.size,
fileType: file.type,
type: this.type,
file_name: file.name,
dataset_id: this.datasetId,
}).then(res => {
const data = res.data;
file.uploadID = data.uploadID;
file.uuid = data.uuid;
if (file.uploadID && file.uuid) {
file.chunks = '';
this.breakpointUpload(file);
} else {
console.log('getNewMultipart Error', file);
this.uploadError(file, info);
this.updateFileStatus(file, this.$t('modelManage.uploadFailed'), 0, 2);
}
return file;
}).catch(err => {
console.log('getNewMultipart', err);
this.uploadError(file, info);
this.updateFileStatus(file, this.$t('modelManage.uploadFailed'), 0, 2);
return err;
});
},
breakpointUpload(file) {
const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
const fileReader = new FileReader();
const time = new Date().getTime();
const chunkSize = uploadChunkSize;
const chunks = Math.ceil(file.size / chunkSize);
const _this = this;
let currentChunk = 0;
const successChunks = [];
const successParts = file.chunks.split(",");
for (let i = 0; i < successParts.length; i++) {
successChunks[i] = successParts[i].split("-")[0];
}
const urls = [];
const etags = [];
const checkSuccessChunks = () => {
const index = successChunks.indexOf((currentChunk + 1).toString());
if (index == -1) {
return false;
}
return true;
}

const getUploadChunkUrl = async (currentChunk, partSize) => {
const res = await getMultipartUrl({
uuid: file.uuid,
uploadID: file.uploadID,
size: partSize,
chunkNumber: currentChunk + 1,
type: _this.type,
file_name: file.name,
dataset_id: this.datasetId,
});
urls[currentChunk] = res.data.url;
};

const uploadMinioNewMethod = async (url, e) => {
const xhr = new XMLHttpRequest();
xhr.open("PUT", url, false);
if (_this.type == 0) {
xhr.setRequestHeader("Content-Type", "text/plain");
xhr.send(e.target.result);
const etagValue = xhr.getResponseHeader("etag");
etags[currentChunk] = etagValue;
} else if (_this.type == 1) {
xhr.setRequestHeader("Content-Type", "");
xhr.send(e.target.result);
const etagValue = xhr.getResponseHeader("ETag");
etags[currentChunk] = etagValue;
}
}

const uploadChunk = async (e) => {
try {
if (!checkSuccessChunks()) {
const start = currentChunk * chunkSize;
const partSize = start + chunkSize >= file.size ? file.size - start : chunkSize;
// 获取分片上传url
await getUploadChunkUrl(currentChunk, partSize);
if (urls[currentChunk] != '') {
await uploadMinioNewMethod(urls[currentChunk], e);
if (etags[currentChunk] != '') {
} else {
console.log("上传到minio uploadChunk etags[currentChunk] == ''");
}
} else {
console.log("uploadChunk urls[currentChunk] != ''");
}
}
} catch (error) {
console.log(error);
}
}

const completeUpload = async () => {
return await setCompleteMultipart({
uuid: file.uuid,
uploadID: file.uploadID,
file_name: file.name,
size: file.size,
type: _this.type,
dataset_id: _this.datasetId,
});
}

function loadNext() {
const start = currentChunk * chunkSize;
const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}

fileReader.onload = async (e) => {
try {
await uploadChunk(e);
fileReader.abort();
currentChunk++;
if (currentChunk < chunks) {
console.log(`第${currentChunk}个分片上传完成, 开始第${currentChunk + 1}/${chunks}个分片上传`);
this.updateFileStatus(file, this.$t('modelManage.uploading'), Number(((currentChunk / chunks) * 100).toFixed(2)), 3);
loadNext();
} else {
try {
await completeUpload();
console.log(`文件上传完成:${file.name} \n分片:${chunks} 大小:${file.size} 用时:${(new Date().getTime() - time) / 1000} s`);
this.uploadLength++;
this.uploadSuccessLength++;
this.updateFileStatus(file, this.$t('modelManage.uploadSuccess'), 100, 0);
this.uploadSuccess(file);
} catch (err) {
const info = this.$t('modelManage.uploadFailed');
console.log(info, file)
this.uploadLength++;
this.uploadError(file, info);
this.updateFileStatus(file, info, Number(((currentChunk / chunks) * 100).toFixed(2)) - 1, 2);
}
}
} catch (err) {
console.log(err);
const info = this.$t('modelManage.uploadFailed');
console.log(info, file)
this.uploadLength++;
this.uploadError(file, info);
this.updateFileStatus(file, info, Number(((currentChunk / chunks) * 100).toFixed(2)) - 1, 2);
}
};
console.log("上传分片...");
loadNext();
},
resetFileStatus() {
this.uploadFiles = [];
this.uploadLength = 0;
this.uploadSuccessLength = 0;
this.uploadStatusList = [];
},
updateFileStatus(file, status, progress, infoCode, failedInfo = "") {
this.uploadStatusList.forEach((item, index) => {
if (item.uploadUuid === file.__uuid) {
this.uploadStatusList[index].status = status;
this.uploadStatusList[index].progress = progress;
this.uploadStatusList[index].infoCode = infoCode;
this.uploadStatusList[index].failedInfo = failedInfo;
}
});
},
uploadError(file, info) {
this.uploadFinishCheck(file);
},
uploadSuccess(file) {
this.uploadFinishCheck(file);
},
uploadFinishCheck(file) {
console.log('uploadFinishCheck', file, this.uploadLength, '/', this.uploadFiles.length);
if (this.uploadLength === this.uploadFiles.length && this.uploadFiles.length != 0) {
console.log('All file has finish, success ' + this.uploadSuccessLength);
this.uploading = false;
this.resolveHandler && this.resolveHandler({
status: '0',
total: this.uploadFiles.length,
success: this.uploadSuccessLength,
msg: `处理完成,总数${this.uploadFiles.length},成功${this.uploadSuccessLength}`,
files: this.uploadFiles,
filesStatus: this.uploadStatusList,
});
}
},
checkFiles(file) {
return true;
},
upload() {
if (this.uploading) return;
return new Promise((resolve, reject) => {
this.resolveHandler = resolve;
this.rejectHandler = reject;
const fileList = this.selectList;
if (!fileList.length) {
this.$message({
type: 'error',
message: '请先选择文件',
});
resolve({
status: '1',
msg: '请选择文件',
});
return;
};
for (let i = 0, iLen = fileList.length; i < iLen; i++) {
if (!this.checkFiles(fileList[i], true)) {
resolve({
status: '1',
msg: '文件不合法'
});
return;
}
}
this.resetFileStatus();
this.uploadFiles = fileList;
this.uploading = true;
for (let i = 0, iLen = fileList.length; i < iLen; i++) {
const file = fileList[i];
file.dataset_id = this.datasetId;
file.__uuid = Math.random();
this.calcFileMd5(file).then(res => { // 计算MD5
this.getChunksInfo(file).then(res => { // 获取Chunk信息
if (file.uploadID == '' || file.uuid == '') { // 未上传过
this.newUpload(file);
} else if (file.uploaded == '1') { // 已上传成功
if (file.attachID == '0') { // 删除数据集记录,未删除文件
console.log(`file.attachID == '0'`);
}
this.uploadLength++;
// 同一数据集上传同一个文件
if (file._datasetID) {
const info = `${this.$t('modelManage.fileHasAlreadyInTheModel')} ${file._modelName}`;
this.uploadError(file, info);
this.updateFileStatus(file, this.$t('modelManage.uploadFailed'), 0, 1, info);
} else { // 秒传
this.uploadSuccessLength++;
this.updateFileStatus(file, this.$t('modelManage.uploadSuccess'), 100, 0);
this.uploadSuccess(file);
}
console.log(file.name, '文件处理完成');
} else { // 断点续传
this.breakpointUpload(file);
}
}).catch(err => {
console.info('getChunksInfo', err);
this.uploadLength++;
this.uploadError(file, this.$t('modelManage.uploadFailed'));
this.updateFileStatus(file, this.$t('modelManage.uploadFailed'), 0, 2);
});
}).catch(err => {
console.info('calcFileMd5', err);
this.uploadLength++;
this.uploadError(file, this.$t('modelManage.uploadFailed'));
this.updateFileStatus(file, this.$t('modelManage.uploadFailed'), 0, 2);
});
}
});

},
check() {
const fileList = this.selectList;
if (!fileList.length) {
this.errStatus = true;
return false;
}
for (let i = 0, iLen = fileList.length; i < iLen; i++) {
if (!this.checkFiles(fileList[i])) {
this.errStatus = true;
return fasle;
}
}
this.errStatus = false;
return true;
},
},
beforeMount() { }
};
</script>

<style scoped lang="less">
.dataset-file-uploader {
display: flex;

.content {
flex: 1;
display: flex;

.file-list-c {
margin-right: 5px;
flex: 1;
min-height: 32px;
border-radius: 4px;
border: 1px solid #DCDFE6;
box-sizing: border-box;
color: #606266;
padding: 0 15px;

.file-item {
line-height: 32px;
font-size: 13px;
}

.file-item-placeholder {
line-height: 32px;
color: rgba(0, 0, 0, 0.6);
opacity: 0.45 !important;
font-size: 13px;
}

&.error {
color: #9f3a38;
background: #fff6f6;
border-color: #e0b4b4;
}
}

.btn-select {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: rgb(3, 102, 214);

i {
margin-right: 2px;
}
}
}
}
</style>

+ 210
- 0
web_src/vuepages/pages/modelbase/components/ModelBaseDatasetSelect.vue View File

@@ -0,0 +1,210 @@
<template>
<div class="dataset-select">
<div class="title">
<span :class="required ? 'required' : ''">{{ title }}</span>
</div>
<div class="content" :class="errStatus ? 'error' : ''">
<div class="tab-item-c">
<div class="tab-item" :class="tabIndex == 0 ? 'tab-item-focus' : ''" @click="changeTab(0)">示例数据集</div>
<div class="tab-item" :class="tabIndex == 1 ? 'tab-item-focus' : ''" @click="changeTab(1)">本地上传</div>
<div class="tab-item" :class="tabIndex == 2 ? 'tab-item-focus' : ''" @click="changeTab(2)">选择平台数据集</div>
</div>
<div class="tab-content-c">
<div v-show="tabIndex == 0" class="tab-content">
<div style="display:flex;align-items:center;height:34px;">
<el-radio-group v-model="sampleDataset">
<el-radio :label="1">文本分类数据集</el-radio><a class="download-btn" download
href="https://openi.pcl.ac.cn/attachments/cc5f7d3f-8be0-482a-8982-ae6c2f0d677b?type=1">(下载)</a>
<el-radio :label="2">中英翻译数据集</el-radio><a class="download-btn" download
href="https://openi.pcl.ac.cn/attachments/f5eb1608-d3cb-4cbe-8df1-8eb0903e0edf?type=1">(下载)</a>
<el-radio :label="3">情感分类数据集</el-radio><a class="download-btn" download
href="https://openi.pcl.ac.cn/attachments/e9eb36c5-1eea-4138-9de2-7d2ddcad19d9?type=1">(下载)</a>
</el-radio-group>
</div>
</div>
<div v-show="tabIndex == 1" class="tab-content">
<div>
<DatasetFileUploader :datasetId="datasetId" ref="datasetFileUploaderRef" :type="1"></DatasetFileUploader>
</div>
</div>
<div v-show="tabIndex == 2" class="tab-content">
<div class="platform-dataset">
<DatasetSelect ref="datasetSelectRef" :userName="userName" :repoName="repoName" :showTitle="false" :type="1"
:maxCount="1">
</DatasetSelect>
</div>
</div>
</div>
<div class="tips-how" v-if="tabIndex != 0">
<a target="_blank" href="https://openi.pcl.ac.cn/PCL-Platform.Intelligence/pcl_pangu/src/branch/master/docs/README_DATASET.md">如何构建数据集</a>
</div>
</div>
</div>
</template>

<script>
import DatasetFileUploader from './DatasetFileUploader.vue';
import DatasetSelect from './cloudbrain/DatasetSelect.vue';

export default {
name: "ModelBaseDatasetSelect",
props: {
title: { type: String, default: "数据集" },
required: { type: Boolean, default: true },
oriData: { type: Array, default: () => [] },
tabindex: { type: Number, default: 0 },
exampleType: { type: Number, default: 1 },
userName: { type: String, default: '' },
repoName: { type: String, default: '' },
datasetId: { type: String, default: '' },
},
components: { DatasetFileUploader, DatasetSelect },
data() {
return {
tabIndex: 0,

sampleDataset: 1,
errStatus: false,
};
},
watch: {
tabindex: {
handler(newVal) {
this.tabIndex = newVal;
},
immediate: true,
},
exampleType: {
handler(newVal) {
this.sampleDataset = newVal;
},
immediate: true,
},
oriData: function (val) {
this.name = val;
},
},
methods: {
changeTab(tab) {
this.tabIndex = tab;
},
getPlatformDataset() {
return [...this.$refs.datasetSelectRef.selectList];
},
check() {
if (this.tabIndex == 0 && this.sampleDataset >= 1 && this.sampleDataset <= 3) {
return true;
}
if (this.tabIndex == 1) {
return this.$refs.datasetFileUploaderRef.check()
}
if (this.tabIndex == 2) {
return this.$refs.datasetSelectRef.check()
}
return false;
},
localUpload() {
return this.$refs.datasetFileUploaderRef.upload();
}
},
};
</script>

<style scoped lang="less">
.dataset-select {
display: flex;
margin-bottom: 28px;

.title {
width: 200px;
text-align: right;
margin-right: 24px;
color: #101010;
font-size: 14px;
display: flex;
justify-content: flex-end;
padding-top: 8px;

.required {
position: relative;

&::after {
position: absolute;
content: "*";
top: -3px;
right: -10px;
color: red;
}
}
}

.content {
flex: 1;

.tab-item-c {
display: flex;

.tab-item {
border: 1px solid #DCDFE6;
display: flex;
justify-content: center;
align-items: center;
height: 36px;
padding: 0 20px;
cursor: pointer;
border-right: none;
color: rgb(96, 98, 102);
background: rgb(245, 245, 246);

&:first-child {
border-radius: 5px 0px 0px 0px;
}

&:last-child {
border-right: 1px solid #DCDFE6;
border-radius: 0px 5px 0px 0px;
}

&.tab-item-focus {
color: rgb(50, 145, 248);
background: rgb(255, 255, 255);
border-bottom: none;
}
}
}

.tab-content-c {
margin-top: -1px;
border: 1px solid #DCDFE6;
border-radius: 0px 5px 5px 5px;
padding: 20px 16px;

.tab-content {
min-height: 34px;
}
}

.tips-how {
margin-top: 5px;
padding-left: 4px;

a {
text-decoration: underline;
}
}

.download-btn {
position: relative;
display: inline-block;
font-size: 12px;
margin-left: -20px;
margin-right: 30px;
}
}
}

.platform-dataset {
.dataset-select {
margin-bottom: 0;
}
}</style>

+ 181
- 0
web_src/vuepages/pages/modelbase/components/TopHeader.vue View File

@@ -0,0 +1,181 @@
<template>
<div>
<div class="bg-container">
<div class="bg-00"></div>
<div class="bg-01"></div>
<div class="bg-02"></div>
<div class="bg-03"></div>
<div class="bg-04">
<div class="bg-04-1"></div>
<div class="bg-04-2"></div>
<div class="bg-04-3"></div>
<div class="bg-04-4"></div>
<div class="bg-04-5"></div>
<div class="bg-04-6"></div>
</div>
<div class="content ui container">
<slot name="default"></slot>
</div>
</div>
</div>
</template>

<script>

export default {
name: "TopHeader",
props: {
// menu: { type: String, default: '' },
},
components: {

},
data() {
return {
list: [],
};
},
methods: {},
mounted() { },
};
</script>

<style scoped lang="less">
.bg-container {
height: 200px;
width: 100%;
overflow: hidden;
position: relative;

.bg-00 {
position: absolute;
right: 0;
top: 0;
height: 100%;
width: 50%;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(0.11899999999999993%2C%201.217%2C%20-0.0901857098765432%2C%200.11899999999999993%2C%200.269%2C%20-0.22)%22%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.47%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23e5e7eb%22%20stop-opacity%3D%220.3%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}

.bg-01 {
position: absolute;
top: 0;
left: 50%;
z-index: 1;
width: 2501px;
height: 400px;
border-radius: 100%;
transform: translate(-50%, -50%);
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(0.06299999999999999%2C%201.5829999999999997%2C%20-0.06686206253204435%2C%200.06299999999999999%2C%200.339%2C%20-0.115)%22%3E%3Cstop%20stop-color%3D%22%23c4e4db%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%236dffd0%22%20stop-opacity%3D%221%22%20offset%3D%220.43%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%239eb3ff%22%20stop-opacity%3D%221%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}

.bg-02 {
position: absolute;
top: -180px;
right: 1%;
width: 70%;
height: 410px;
border-radius: 100%;
transform-origin: 583.5px 205px;
z-index: 2;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(-0.002000000000000203%2C%200.9999999999999999%2C%20-0.12343149845545413%2C%20-0.002000000000000203%2C%200%2C%200)%22%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.2%22%20offset%3D%220.34%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220%22%20offset%3D%220.76%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}

.bg-03 {
position: absolute;
top: -100px;
right: 10%;
transform-origin: 501px 150px;
width: 60%;
height: 300px;
border-radius: 100%;
z-index: 3;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3CradialGradient%20id%3D%221%22%20cx%3D%220%22%20cy%3D%220%22%20r%3D%221%22%20gradientTransform%3D%22matrix(-0.6689999999999999%2C%200.225%2C%20-0.01901959553946%2C%20-0.6308669999999998%2C%200.7%2C%200.342)%22%3E%3Cstop%20stop-color%3D%22%23fbffb1%22%20stop-opacity%3D%220.6%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23fbffb8%22%20stop-opacity%3D%220%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}

.bg-04 {
position: absolute;
z-index: 4;
left: 10%;

.bg-04-1 {
position: absolute;
top: -25px;
left: 2px;
z-index: 4;
transform-origin: 71px 25px;
width: 142px;
height: 50px;
border-radius: 100%;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736766e-17%2C%201%2C%20-0.12398333663955563%2C%206.123233995736766e-17%2C%200.5%2C%200)%22%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.3%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.1%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}

.bg-04-2 {
position: absolute;
top: 40px;
left: 23px;
z-index: 4;
transform-origin: 50px 17.5px;
width: 100px;
height: 35px;
border-radius: 100%;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736766e-17%2C%201%2C%20-0.12249999999999998%2C%206.123233995736766e-17%2C%200.5%2C%200)%22%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.4%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}

.bg-04-3 {
position: absolute;
top: 88px;
left: 57px;
z-index: 4;
transform-origin: 38.5px 13.5px;
width: 77px;
height: 27px;
border-radius: 100%;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736766e-17%2C%201%2C%20-0.12295496711081128%2C%206.123233995736766e-17%2C%200.5%2C%200)%22%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.45%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}

.bg-04-4 {
position: absolute;
top: 122px;
left: 105px;
z-index: 4;
transform-origin: 29px 10.5px;
width: 58px;
height: 21px;
border-radius: 100%;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736766e-17%2C%201%2C%20-0.13109393579072534%2C%206.123233995736766e-17%2C%200.5%2C%200)%22%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.5%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}

.bg-04-5 {
position: absolute;
top: 145px;
left: 144px;
z-index: 4;
transform-origin: 22.5px 8.5px;
width: 45px;
height: 17px;
border-radius: 100%;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736766e-17%2C%201%2C%20-0.14271604938271604%2C%206.123233995736766e-17%2C%200.5%2C%200)%22%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.55%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}

.bg-04-6 {
position: absolute;
top: 162px;
left: 176px;
z-index: 4;
transform-origin: 19.5px 7px;
width: 39px;
height: 14px;
border-radius: 100%;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736766e-17%2C%201%2C%20-0.12886259040105194%2C%206.123233995736766e-17%2C%200.5%2C%200)%22%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.6%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}
}

.content {
position: absolute;
z-index: 5;
height: 100%;
left: 50%;
transform: translateX(-50%);
}
}
</style>

+ 583
- 0
web_src/vuepages/pages/modelbase/components/cloudbrain/DatasetSelect.vue View File

@@ -0,0 +1,583 @@
<template>
<div class="dataset-select">
<div class="title" v-if="showTitle"><span :class="required ? 'required' : ''">{{ selfTitle }}</span></div>
<div class="content">
<div class="dataset-list-c" :class="errStatus ? 'error' : ''">
<div class="dataset-item" v-for="(item, index) in selectList" :key="item.id">
{{ item.name }};
</div>
<div v-if="selectList.length == 0" class="dataset-item-placeholder">
{{ $t('datasetObj.dataset_select_placeholder') }}
</div>
</div>
<div class="btn-select" @click="dlgShow = true">
<i class="el-icon-plus"></i>
<span>{{ $t('datasetObj.dataset_select') }}</span>
</div>
</div>
<el-dialog class="dataset-dlg" :visible.sync="dlgShow" :title="$t('datasetObj.dataset_select')" width="1000px"
:modal="true" :close-on-click-modal="false" :show-close="true" :destroy-on-close="false" :before-close="beforeClose"
@open="open" @closed="closed">
<div class="dlg-content">
<div class="left-area" v-loading="dlgLoading">
<div class="dataset-tabs-c">
<el-tabs class="dataset-tabs" v-model="dlgActiveName" @tab-click="dlgTabClick">
<el-tab-pane :label="$t('datasetObj.dataset_current_repo')" name="first"></el-tab-pane>
<el-tab-pane :label="$t('datasetObj.dataset_my_upload')" name="second"></el-tab-pane>
<el-tab-pane :label="$t('datasetObj.dataset_public')" name="third"></el-tab-pane>
<el-tab-pane :label="$t('datasetObj.dataset_collected')" name="fourth"></el-tab-pane>
</el-tabs>
<el-input class="search-inp" :placeholder="$t('datasetObj.dataset_search_placeholder')"
v-model="dlgSearchValue" @keyup.enter.native="inputSearch">
<div slot="suffix" class="search-inp-icon" @click="inputSearch">
<i class="el-icon-search"></i>
</div>
</el-input>
</div>
<el-tree :data="dlgDatasetTreeData" ref="dlgTreeRef" highlight-current show-checkbox node-key="id"
:default-expanded-keys="dlgInitTreeNode" :props="dlgTreeProps" :index="10" accordion
@check="onTreeCheckChange">
<span slot-scope="{ node, data }" class="slot-wrap">
<span v-if="data.parent" class="custom-tree-node">
<el-tooltip v-if="data.Description" placement="top-start">
<div slot="content" class="multiple-wrap"> {{ data.Description }}</div>
<span class="dataset-title dataset-nowrap">
<div class="dataset_flex">
<span style="flex: inherit" class="dataset-nowrap">{{ node.label }}</span>
<img v-if="data.Recommend" style="margin-left: 0.4rem" src="/img/jian.svg" />
</div>
</span>
</el-tooltip>
<span v-else class="dataset-title dataset-nowrap">
<div class="dataset_flex">
<span style="flex: inherit" class="dataset-nowrap">{{ node.label }}</span>
<img v-if="data.Recommend" style="margin-left: 0.4rem" src="/img/jian.svg" />
</div>
</span>
<span class="dataset-repolink dataset-nowrap" @click.stop="return false;">
<i class="ri-links-line" style="color: #21ba45; margin-right: 0.3rem"
:title="$t('datasetObj.dataset_relate')"
v-if="dlgActiveName == 'first' && '/' + data.Repo.OwnerName + '/' + data.Repo.Name !== '/' + userName + '/' + repoName"></i>
<a :href="'/' + data.Repo.OwnerName + '/' + data.Repo.Name + '/datasets'" target="_blank"
:title="data.Repo.OwnerName + '/' + data.Repo.Alias">
{{ data.Repo.OwnerName }}/{{ data.Repo.Alias }}
</a>
</span>
</span>
<span v-else style="display: flex">
<span class="dataset-nowrap" :title="node.label">
{{ node.label }}
</span>
<span class="zip-loading" v-if="data.DecompressState == 2">
{{ $t('datasetObj.dataset_unziping') }}
</span>
<span class="unzip-failed" v-if="data.DecompressState == 3">
{{ $t('datasetObj.dataset_unzip_failed') }}
</span>
<span class="unzip-failed" v-if="exceedSize && data.Size > exceedSize">
{{ $t('datasetObj.dataset_exceeds_failed') }}{{ exceedSize / (1000 * 1000 * 1000) }}G
</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>
</div>
</div>
<div class="right-area">
<div class="right-title"><span>{{ $t('datasetObj.dataset_selected') }}</span></div>
<div class="right-selected-list">
<el-checkbox-group v-model="dlgSelectedDataset">
<el-checkbox v-for="(item, index) in dlgSelectedDatasetList" :key="item.id" :label="item.id"
:true-label="item.id" :title="item.label" @change="(checked) => dlgChangeSelect(checked, item)">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</div>
<div class="right-btn-c">
<el-button type="primary" @click="confirm">{{ $t('datasetObj.dataset_ok') }}</el-button>
</div>
</div>
</div>
</el-dialog>
</div>
</template>

<script>
import { getCurrentRepoDataset, getMyUploadedDataset, getPulicDataset, getMyFavoriteDataset } from '~/apis/modules/dataset';

const apiMap = {
'first': getCurrentRepoDataset,
'second': getMyUploadedDataset,
'third': getPulicDataset,
'fourth': getMyFavoriteDataset,
};

export default {
name: "DatasetSelect",
props: {
title: { type: String, default: '' },
showTitle: { type: Boolean, default: true },
required: { type: Boolean, default: true },
type: { type: Number, default: 0 },
maxCount: { type: Number, default: 5 },
userName: { type: String, default: '' },
repoName: { type: String, default: '' },
oriData: { type: Array, default: () => [] },
},
data() {
return {
selectList: [],
dlgShow: false,
dlgLoading: false,
dlgActiveName: 'first',
dlgSearchValue: '',

dlgTreeProps: {
children: "Attachments",
label: "label",
},
dlgDatasetTreeData: [],
dlgInitTreeNode: [],

dlgPage: 1,
dlgPageSize: 5,
dlgTotal: 0,

dlgSelectedDataset: [],
dlgSelectedDatasetList: [],

exceedSize: 0,

errStatus: false,
};
},
computed: {
selfTitle() {
return this.title || this.$t('dataset_label');
}
},
watch: {
oriData: function (val) {

},
},
methods: {
open() {
this.dlgTotal = 0;
this.dlgPage = 1;
this.dlgSelectedDataset = [];
this.dlgSelectedDatasetList = [];
for (let i = 0, iLen = this.selectList.length; i < iLen; i++) {
const item = this.selectList[i];
this.dlgSelectedDataset.push(item.id);
this.dlgSelectedDatasetList.push({
id: item.id,
label: item.name,
});
}
this.searchDatasetData();
},
beforeClose(done) {
done();
},
closed() { },
dlgTabClick(tab, event) {
this.dlgTotal = 0;
this.dlgPage = 1;
this.searchDatasetData();
},
transformTreeData(data) {
return data.reduce((preParent, curParent) => {
curParent.id = curParent.ID;
curParent.disabled = true;
curParent.parent = true;
curParent.label = curParent.Title;
const childrenData = curParent.Attachments.reduce(
(preChild, curchild) => {
curchild.id = curchild.UUID;
if (curchild.DecompressState !== 1) {
curchild.disabled = true;
}
if (curchild.Size > this.exceedSize && this.exceedSize) {
curchild.disabled = true;
}
curchild.ref = 'dlgTreeRef';
curchild.label = curchild.Name;
preChild.push(curchild);
return preChild;
},
[]
);
preParent.push(curParent);
return preParent;
}, []);
},
inputSearch() {
this.dlgTotal = 0;
this.dlgPage = 1;
this.searchDatasetData();
},
searchDatasetData() {
const tabName = this.dlgActiveName;
const getApi = apiMap[tabName];
this.dlgLoading = true;
getApi({
userName: this.userName,
repoName: this.repoName,
q: this.dlgSearchValue.trim(),
type: this.type,
page: this.dlgPage,
}).then(res => {
this.dlgLoading = false;
const data = JSON.parse(res.data.data);
this.dlgDatasetTreeData = this.transformTreeData(data);
this.dlgInitTreeNode = this.dlgDatasetTreeData[0]?.id
? [this.dlgDatasetTreeData[0].id]
: [];
this.dlgTotal = parseInt(res.data.count);
const setCheckedKeysList = this.dlgDatasetTreeData.reduce((pre, cur) => {
cur.Attachments.forEach((item) => {
if (this.dlgSelectedDataset.includes(item.id)) {
pre.push(item.id);
}
});
return pre;
}, []);
this.$refs.dlgTreeRef.setCheckedKeys(setCheckedKeysList);
}).catch(err => {
this.dlgLoading = false;
console.log(err);
});
},
dlgChangeSelect(checked, data) {
this.$refs.dlgTreeRef.setChecked(data.id, false, false);
const index = this.dlgSelectedDatasetList.findIndex((item) => {
return item.id === data.id;
});
this.dlgSelectedDatasetList.splice(index, 1);
this.dlgSelectedDataset = this.dlgSelectedDatasetList.map(item => item.id);
},
dlgPageChange(page) {
this.dlgPage = page;
this.searchDatasetData();
},
onTreeCheckChange(data) {
if (
this.dlgSelectedDatasetList.length === 0 ||
this.dlgSelectedDatasetList.every((item) => item.id !== data.id)
) {
if (
this.dlgSelectedDatasetList.some((item) => {
const itemIndex = item.label.lastIndexOf('.')
const dataIndex = data.label.lastIndexOf('.')
return item.label.substr(0, itemIndex) === data.label.substr(0, dataIndex);
})
) {
this.$refs.dlgTreeRef.setChecked(data.id, false, false);
this.$message.warning(this.$t('datasetObj.dataset_not_equal_file'));
} else if (this.dlgSelectedDatasetList.length === this.maxCount) {
this.$refs.dlgTreeRef.setChecked(data.id, false, false);
this.$message.error(this.$t('datasetObj.dataset_most', { msg: this.maxCount }));
} else {
this.dlgSelectedDatasetList.push(data);
}
} else {
const index = this.dlgSelectedDatasetList.findIndex((item) => {
return item.id === data.id;
});
this.dlgSelectedDatasetList.splice(index, 1);
}
this.dlgSelectedDataset = this.dlgSelectedDatasetList.map((item) => {
return item.id;
});
},
confirm() {
const ids = this.dlgSelectedDatasetList.map(item => item.id);
const names = this.dlgSelectedDatasetList.map(item => item.label);
this.selectList.splice(0, Infinity);
for (let i = 0, iLen = ids.length; i < iLen; i++) {
this.selectList.push({
id: ids[i],
name: names[i]
});
}
if (this.selectList.length) {
this.errStatus = false;
}
this.dlgShow = false;
},
check() {
if (!this.selectList.length) {
this.errStatus = true;
return false;
}
this.errStatus = false;
return true;
},
},
beforeMount() { }
};
</script>

<style scoped lang="less">
.dataset-select {
display: flex;
margin-bottom: 28px;

.title {
width: 200px;
text-align: right;
margin-right: 24px;
color: #101010;
font-size: 14px;
display: flex;
justify-content: flex-end;
padding-top: 6px;

.required {
position: relative;

&::after {
position: absolute;
content: "*";
top: -3px;
right: -10px;
color: red;
}
}
}

.content {
flex: 1;
display: flex;

.dataset-list-c {
margin-right: 5px;
flex: 1;
min-height: 32px;
border-radius: 4px;
border: 1px solid #DCDFE6;
box-sizing: border-box;
color: #606266;
padding: 0 15px;

.dataset-item {
line-height: 32px;
font-size: 13px;
}

.dataset-item-placeholder {
line-height: 32px;
color: rgba(0, 0, 0, 0.6);
opacity: 0.45 !important;
font-size: 13px;
}

&.error {
color: #9f3a38;
background: #fff6f6;
border-color: #e0b4b4;
}
}

.btn-select {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: rgb(3, 102, 214);

i {
margin-right: 2px;
}
}
}
}

.dataset-dlg {
/deep/.el-dialog__body {
padding-top: 0;
}

.dlg-content {
display: flex;
min-height: 300px;

.left-area {
flex: 1;
margin-right: 15px;
border-right: 1px solid rgb(245, 245, 246);
padding-right: 15px;
position: relative;
overflow: hidden;

.dataset-tabs-c {
display: flex;
align-items: center;

.dataset-tabs {
flex: 1;
overflow: hidden;
margin-right: 5px;
}

.search-inp {
overflow: hidden;
width: 200px;
z-index: 5;
position: relative;
margin-top: -10px;

.search-inp-icon {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
width: 22px;
cursor: pointer;
}
}
}

.pagination-c {
margin-top: 25px;
text-align: center;
}
}

.right-area {
width: 300px;
padding-right: 30px;
position: relative;


.right-title {
font-size: 14px;
height: 40px;
text-align: left;
color: rgb(0, 102, 255);
line-height: 40px;
}

.right-selected-list {
margin: 14px 0;
}

.right-btn-c {
text-align: right;
position: absolute;
bottom: 15px;
width: 100%;
padding-right: 30px;
}
}
}
}

.el-tree {
max-height: 400px;
overflow-y: auto;
overflow-x: hidden;
position: relative;
cursor: default;
background: #fff;
color: #606266;
font-family: SourceHanSansSC-medium;
}

.custom-tree-node {
display: flex;
align-items: center;
justify-content: space-between;
}

.custom-tree-node .dataset-title {
font-size: 14px;
color: #101010;
font-weight: 600;
flex: 1;
}

.custom-tree-node .dataset-repolink {
flex: 1;
text-align: right;
font-size: 12px;
}

.el-tree /deep/ .el-tree-node__content {
height: 40px;
background-color: #f5f5f6;
}

.el-tree /deep/ .el-tree-node__children .el-tree-node__content {
height: 30px;
background-color: #fff;
line-height: 20px;
font-size: 12px;
}

/deep/ .el-checkbox-group .el-checkbox {
max-width: 100%;
min-width: 80%;
}

/deep/ .el-checkbox-group .el-checkbox .el-checkbox__label {
max-width: 100%;
overflow: hidden;
vertical-align: middle;
text-overflow: ellipsis;
}

.dataset-nowrap {
overflow: hidden;
text-overflow: ellipsis;
}

.slot-wrap {
flex: 1;
padding-right: 2rem;
max-width: 93%;
}

.multiple-wrap {
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
display: -webkit-box;
max-width: 400px;
overflow: hidden;
}

.unzip-failed {
margin-left: 1rem;
color: red;
}

.zip-loading {
margin-left: 1rem;
color: #fcca00;
}

.dataset-search-vue {
z-index: 9999;
position: absolute;
right: 31%;
height: 30px;
top: 6px;
}

.select-dataset-label {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 1rem;
white-space: nowrap;
}

.dataset_flex {
display: flex;
align-items: center;
}
</style>

+ 136
- 0
web_src/vuepages/pages/modelbase/components/cloudbrain/LoadingMask.vue View File

@@ -0,0 +1,136 @@
<template>
<div v-if="loading" class="loading-mask">
<div class="loading-wrap">
<div class="loading-icon">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<div class="loading-tips"> {{ tipsContent }}</div>
</div>
</div>
</template>

<script>
export default {
name: "LoadingMask",
props: {
loading: { type: Boolean, default: false },
tips: { type: String, default: '' },
},
data() {
return {};
},
computed: {
tipsContent() {
return this.tips || '加载中,请稍后'
}
},
};
</script>

<style scoped lang="less">
.loading-mask {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
filter: alpha(opacity=60);
background-color: rgba(119, 119, 119, 0.8);
z-index: 1000;
color: #000000;

.loading-wrap {
width: 20%;
height: 25%;
border: 1px solid #bbb;
background-color: #fff;
margin: auto;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
border-radius: 20px;

.loading-icon {
width: 50px;
height: 40px;
text-align: center;
font-size: 10px;
display: block;

>div {
background-color: green;
height: 100%;
width: 6px;
display: inline-block;
-webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
animation: sk-stretchdelay 1.2s infinite ease-in-out;
}

.rect2 {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}

.rect3 {
-webkit-animation-delay: -1s;
animation-delay: -1s;
}

.rect4 {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}

.rect5 {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}
}

.loading-tips {
margin-top: 10%;
font-size: 14px;
color: #101010;
font-family: SourceHanSansSC-regular;
}
}
}

@-webkit-keyframes sk-stretchdelay {

0%,
40%,
100% {
-webkit-transform: scaleY(0.4);
}

20% {
-webkit-transform: scaleY(1);
}
}

@keyframes sk-stretchdelay {

0%,
40%,
100% {
transform: scaleY(0.4);
-webkit-transform: scaleY(0.4);
}

20% {
transform: scaleY(1);
-webkit-transform: scaleY(1);
}
}
</style>

+ 312
- 0
web_src/vuepages/pages/modelbase/components/cloudbrain/ResourceSpecification.vue View File

@@ -0,0 +1,312 @@
<template>
<div class="resource-specification">
<div class="title"><span :class="required ? 'required' : ''">{{ title }}</span></div>
<div class="content" :class="errStatus ? 'error' : ''">
<div class="spec-info">
<el-select class="spec-sel" :class="showPoint ? 'spec-show-point' : ''" v-model="spec" placeholder="请选择资源规格"
@change="changeSpec">
<div slot="prefix" class="spec-sel-icon spec-op-icon">
<div :class="selIconType + '_icon'">{{ selIconType[0] }}</div>
</div>
<el-option v-for="(item, index) in list" :key="item.id" :label="item.specStr" :value="item.id">
<span style="float: left" class="spec-op-icon">
<div :class="item.type + '_icon'">{{ item.type[0] }}</div>
</span>
<span class="spec-op-spec" style="float: left">{{ item.specStr }}</span>
<span class="spec-op-point" style="float: right;" v-if="showPoint">{{ item.pointStr }}</span>
</el-option>
<div slot="prefix" v-if="showPoint" class="spec-sel-point"> {{ selPointStr }} </div>
</el-select>
<div class="self-point-info" v-if="showPoint">
<span>积分余额:<span style="color:red"> {{ blance }}</span> 积分<span v-if="showUseTime">,预计可用 <span
style="color:red">{{ canUseTime }}</span> 小时</span></span>
<span>
<i class="el-icon-question"></i>
<a target="_blank" href="/reward/point/rule">积分获取说明</a>
</span>
</div>
</div>
<div class="resource-descr-c">
<div class="resource-descr">
<i class="el-icon-question"></i>
<a target="_blank" href="/resource_desc">资源说明</a>
</div>
</div>

</div>
</div>
</template>
<script>
import { renderSpecObject } from '~/utils';

export default {
name: "ResourceSpecification",
props: {
title: { type: String, default: "资源规格" },
required: { type: Boolean, default: true },
specData: { type: Array, default: () => [] },
specOri: { type: String, default: '' },
showPoint: { type: Boolean, default: false, },
blance: { type: Number, default: 0 },
workServerNum: { type: Number, default: 1 },
},
data() {
return {
list: [],
spec: '',
selIconType: '',
selPointStr: '',
showUseTime: false,
canUseTime: '',

errStatus: false,
};
},
watch: {
specData: function (val) {
this.renderSpec();
},
showPoint: function (val) {
this.renderSpec();
},
blance: function (val) {
this.renderSpec();
},
specOri: function (val) {
this.spec = val;
this.renderSpec();
}
},
methods: {
renderSpec(specs) {
this.list = this.specData.map((item) => {
return renderSpecObject(item, this.showPoint)
});
this.changeSpec(this.spec);
},
changeSpec(specID) {
const seldSpecItem = this.list.filter(item => item.id == this.spec)[0];
if (seldSpecItem) {
this.selIconType = seldSpecItem.type;
this.selPointStr = seldSpecItem.pointStr;
this.errStatus = false;
}
this.refreshPointInfo();
},
refreshPointInfo() {
if (!this.showPoint) return;
const seldSpecItem = this.list.filter(item => item.id == this.spec)[0];
if (seldSpecItem) {
const unitPrice = seldSpecItem.unit_price;
const blance = this.blance;
const workServerNum = this.workServerNum;
if (unitPrice == 0) {
this.showUseTime = false;
} else {
let canUseTime = Number(blance) / (Number(unitPrice) * Number(workServerNum));
if (Number(blance) < Number(unitPrice) * Number(workServerNum)) {
// 余额不足一个单位单价时可用时间提示为 0
canUseTime = 0;
}
this.canUseTime = canUseTime.toFixed(2);
this.showUseTime = true;
}
}
},
check() {
const seldSpecItem = this.list.filter(item => item.id == this.spec)[0];
if (!seldSpecItem) {
this.errStatus = true;
return false;
}
this.errStatus = false;
return true;
},
getSpecData() {
return this.list.filter(item => item.id == this.spec)[0];
},
},
beforeMount() { }
};
</script>

<style scoped lang="less">
.resource-specification {
display: flex;
margin-bottom: 28px;

.title {
width: 200px;
text-align: right;
margin-right: 24px;
color: #101010;
font-size: 14px;
display: flex;
justify-content: flex-end;
padding-top: 6px;

.required {
position: relative;

&::after {
position: absolute;
content: "*";
top: -3px;
right: -10px;
color: red;
}
}
}

.content {
flex: 1;
display: flex;

.spec-info {
flex: 1;

.spec-sel {
width: 100%;

/deep/.el-input__prefix {
width: 100%;
}

/deep/.el-input__inner {
padding-left: 36px;
}

.spec-sel-icon {
position: absolute;
left: 5px;
height: 96%;
}

.spec-sel-point {
position: absolute;
right: 40px;
display: flex;
align-items: center;
height: 99%;
}

&.spec-show-point {
/deep/.el-input__inner {
padding-right: 120px;
}
}
}

.self-point-info {
padding: 0 10px;
margin-top: 5px;
display: flex;
justify-content: space-between;
font-size: 12px;

i {
margin-right: 4px;
cursor: pointer;
color: rgba(0, 0, 0, 0.87);
}
}
}

.resource-descr-c {
padding-top: 6px;

.resource-descr {
display: flex;
margin-left: 10px;
align-items: center;

i {
margin-right: 5px;
cursor: pointer;
color: rgba(0, 0, 0, 0.87);
}
}
}

&.error {
.spec-sel {
/deep/.el-input__inner {
color: #9f3a38;
background: #fff6f6;
border-color: #e0b4b4;

&:visited {
border-color: #e0b4b4;
}

&:focus {
border-color: #e0b4b4;
}

&:active {
border-color: #e0b4b4;
}
}
}
}
}
}

.spec-op-icon {
margin-right: 6px;
display: flex;
align-items: center;
height: 100%;

._icon {
color: white;
width: 22px;
height: 22px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 100%;
background: rgba(136, 136, 136, 0.5);
}

.CPU_icon {
color: white;
width: 22px;
height: 22px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 100%;
background: rgb(54, 207, 201);
}

.GPU_icon {
color: white;
width: 22px;
height: 22px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 100%;
background: rgb(252, 202, 0);
}

.NPU_icon {
color: white;
width: 22px;
height: 22px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 100%;
background: rgb(123, 50, 178)
}
}

// .spec-op-spec {}

.spec-op-point {
color: rgba(136, 136, 136, 1);
font-size: 14px
}
</style>

+ 227
- 0
web_src/vuepages/pages/modelbase/components/cloudbrain/RunParameters.vue View File

@@ -0,0 +1,227 @@
<template>
<div class="run-parameters">
<div class="title">
<span :class="required ? 'required' : ''">{{ title }}</span>
</div>
<div class="content">
<div class="param-list">
<div class="param-item" v-for="(item, index) in list" :key="item.id">
<div class="param-k-v">
<el-input class="param-k" readonly :class="item.keyError ? 'error' : ''" placeholder="参数名" @input="inputChange(item)"
v-model="item.key"></el-input>
<el-input class="param-v" :class="item.valueError ? 'error' : ''" placeholder="参数值" @input="inputChange(item)"
v-model="item.value"></el-input>
</div>
<div class="param-del-btn">
<i class="trash icon" @click="removeParameter(item)"></i>
</div>
</div>
</div>
<div class="add-param-btn">
<a href="javascript:;" @click="addParameter">
<i class="plus square outline icon"></i>
<span>新增运行参数</span>
</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: "RunParameters",
props: {
title: { type: String, default: "运行参数" },
required: { type: Boolean, default: false },
oriData: { type: Array, default: () => [] },
},
data() {
return {
list: [],
};
},
watch: {
oriData: {
immediate: true,
deep: true,
handler(newVal) {
this.list = newVal.map((item) => {
return {
id: Math.random(),
key: item.k,
value: item.v,
keyError: false,
valueError: false,
}
})
}
}
},
methods: {
addParameter() {
this.list.push({
id: Math.random(),
key: '',
value: '',
keyError: false,
valueError: false,
})
},
removeParameter(item) {
const index = this.list.findIndex((_item) => _item.id == item.id);
this.list.splice(index, 1);
},
inputChange(item) {
if (item.key.trim() != '') {
item.keyError = false;
}
if (item.value.trim() != '') {
item.valueError = false;
}
},
check() {
let hasError = false;
for (let i = 0, iLen = this.list.length; i < iLen; i++) {
const item = this.list[i];
if (item.key.trim() == '') {
hasError = true;
item.keyError = true;
} else {
item.keyError = false;
}
if (item.value.trim() == '') {
hasError = true;
item.valueError = true;
} else {
item.valueError = false;
}
}
return !hasError;
},
getParameter() {
return this.list.map(item => {
return {
label: item.key,
value: item.value
}
})
},
},
};
</script>

<style scoped lang="less">
.run-parameters {
display: flex;
margin-bottom: 28px;

.title {
width: 200px;
text-align: right;
margin-right: 24px;
color: #101010;
font-size: 14px;
display: flex;
justify-content: flex-end;
padding-top: 6px;

.required {
position: relative;

&::after {
position: absolute;
content: "*";
top: -3px;
right: -10px;
color: red;
}
}
}

.content {
flex: 1;

.add-param-btn {
margin-left: 2px;
margin-top: 6px;
display: none;

a {
color: rgba(3, 102, 214, 100);
}
}

.param-list {

.param-item {
display: flex;
align-items: center;
margin-bottom: 12px;

.param-k-v {
flex: 1;
display: flex;

.param-k {
margin-right: 10px;

&.error {
/deep/.el-input__inner {
color: #9f3a38;
background: #fff6f6;
border-color: #e0b4b4;

&:visited {
border-color: #e0b4b4;
}

&:focus {
border-color: #e0b4b4;
}

&:active {
border-color: #e0b4b4;
}
}

}
}

.param-v {
margin-right: 10px;

&.error {
/deep/.el-input__inner {
color: #9f3a38;
background: #fff6f6;
border-color: #e0b4b4;

&:visited {
border-color: #e0b4b4;
}

&:focus {
border-color: #e0b4b4;
}

&:active {
border-color: #e0b4b4;
}
}

}
}
}

.param-del-btn {
width: 20px;
display: none;

i {
cursor: pointer;
}
}
}
}
}
}
</style>

+ 127
- 0
web_src/vuepages/pages/modelbase/components/cloudbrain/TaskName.vue View File

@@ -0,0 +1,127 @@
<template>
<div class="task-name">
<div class="title">
<span :class="required ? 'required' : ''">{{ title }}</span>
</div>
<div class="content" :class="errStatus ? 'error' : ''">
<el-input class="name-input" v-model="taskName" @input="check" maxlength="36" placeholder="任务名称"></el-input>
<div class="tips">只能以小写字母或数字开头且只包含小写字母、数字、_和-,不能以_结尾,最长36个字符。</div>
</div>
</div>
</template>

<script>
import dayjs from 'dayjs';

export default {
name: "TaskName",
props: {
title: { type: String, default: "任务名称" },
required: { type: Boolean, default: true },
userName: { type: String, default: "" },
oriData: { type: String, default: "" },
},
data() {
return {
taskName: '',
errStatus: false,
};
},
watch: {
oriData: function (val) {
this.taskName = val;
},
},
methods: {
check() {
const reg = /^[a-z0-9][a-z0-9\-_]{1,34}[a-z0-9\-]$/;
this.errStatus = !reg.test(this.taskName);
return !this.errStatus;
},
generateName() {
let str = this.userName.toLocaleLowerCase();
const reg1 = /[^a-z0-9_\-]+/g;
const reg2 = /^[_\-]+/g;
const reg3 = /[_]+$/g;
str = str.replace(reg1, '').replace(reg2, '').replace(reg3, '');
str = str.slice(0, 5);
const now = Date.now();
return str + dayjs(now).format('YYYYMMDDHH') + (now / 1000).toFixed(0).slice(-5);
},
getName() {
return this.taskName;
}
},
mounted() {
if (!this.oriData) {
this.taskName = this.generateName();
}
},
};
</script>

<style scoped lang="less">
.task-name {
display: flex;
margin-bottom: 28px;

.title {
width: 200px;
text-align: right;
margin-right: 24px;
color: #101010;
font-size: 14px;
display: flex;
justify-content: flex-end;
padding-top: 6px;

.required {
position: relative;

&::after {
position: absolute;
content: "*";
top: -3px;
right: -10px;
color: red;
}
}
}

.content {
flex: 1;

.name-input {
width: 100%;
}

.tips {
font-size: 12px;
color: rgba(136, 136, 136, 1);
margin-top: 10px;
}

&.error {
.name-input {
/deep/.el-input__inner {
color: #9f3a38;
background: #fff6f6;
border-color: #e0b4b4;

&:visited {
border-color: #e0b4b4;
}

&:focus {
border-color: #e0b4b4;
}

&:active {
border-color: #e0b4b4;
}
}
}
}
}
}
</style>

+ 0
- 0
web_src/vuepages/pages/modelbase/const.js View File


+ 419
- 0
web_src/vuepages/pages/modelbase/create/index.vue View File

@@ -0,0 +1,419 @@
<template>
<div>
<div class="ui container area">
<div class="area-title">新建鹏程·盘古大模型微调任务</div>
<div v-if="alreadyMsgBoxShow">
<div class="err-msg-box-already">
<div class="msg-content">
<i class="ri-information-line"></i>
<div class="msg-content-tip">
<div class="line-1" v-html="$t('cloudbrainObj.sameTaskTips1')"></div>
<div class="line-2" v-html="$t('cloudbrainObj.sameTaskTips2')"></div>
</div>
</div>
</div>
</div>
<div class="area-content">
<div class="content">
<div class="ui container">
<div class="tips-c">
<p><span>*</span> 本次新建的训练任务会放在您名下项目<span>openi-notebook</span>中,如果没有该项目系统会自动新建一个。</p>
</div>
<div class="main-title">基本信息:</div>
<div class="row">
<div class="row-title"><span>模型名称</span></div>
<div class="row-content" style="padding-top:6px;"><span>鹏程 · 盘古</span></div>
</div>
<div class="row">
<div class="row-title"><span class="required">模型规模</span></div>
<div class="row-content">
<el-select v-model="modelSize">
<el-option v-for="(item, index) in modelSizeList" :key="index" :value="item.key"
:label="item.value"></el-option>
</el-select>
</div>
</div>
<TaskName ref="taskNameRef" :userName="userName"></TaskName>
<div class="row">
<div class="row-title"><span>任务描述</span></div>
<div class="row-content">
<el-input type="textarea" :rows="4" placeholder="描述字符不超过255个字符" v-model="descr"
:maxlength="255"></el-input>
</div>
</div>
<div class="main-title">任务配置:</div>
<ModelBaseDatasetSelect ref="modelBaseDatasetSelectRef" :tabindex="datasetTab"
:exampleType="exampleDatasetType" :datasetId="datasetId" :userName="userName" :repoName="repoName">
</ModelBaseDatasetSelect>
<ResourceSpecification ref="resourceSpecificationRef" :specData="specData" :blance="pointBlance"
:showPoint="pointShow" :specOri="spec"></ResourceSpecification>
<RunParameters ref="runParametersRef" :required="true" :oriData="paramsOriData"></RunParameters>
<div class="row">
<div class="row-title"></div>
<div class="row-content" style="padding-top:6px;">
<el-button type="primary" class="btn confirm-btn" size="default" :disabled="alreadyMsgBoxShow"
@click="submitClick" :loading="loading">
提 交</el-button>
<el-button @click="cancel" class="btn" size="default">取 消</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
<LoadingMask :loading="loading" :tips="loadingTips"></LoadingMask>
</div>
</template>

<script>
import TaskName from '../components/cloudbrain/TaskName.vue';
import ModelBaseDatasetSelect from '../components/ModelBaseDatasetSelect.vue';
import ResourceSpecification from '../components/cloudbrain/ResourceSpecification.vue';
import RunParameters from '../components/cloudbrain/RunParameters.vue';
import LoadingMask from '../components/cloudbrain/LoadingMask.vue';
import { getUrlSearchParams, getListValueWithKey, transFileSize, renderSpecStr } from '~/utils';
import { getCheckRepo, getSpecInfo, setFinetuneCreate } from '~/apis/modules/modelbase';
import { getPointAccountInfo } from '~/apis/modules/common';

const alreadyMsgBoxShow = (window.notStopTaskCount || 0) >= 1;

export default {
data() {
return {
userName: '',
repoName: 'openi-notebook',

modelSize: '2.6',
modelSizeList: [{
key: '2.6',
value: '2.6 B',
}],
descr: '',

datasetTab: 0,
exampleDatasetType: 1,
datasetId: '',

spec: '',
specData: [],
pointBlance: 0,
pointShow: false,

paramsOriData: [{ k: 'train_iters', v: '40' }],

alreadyMsgBoxShow: alreadyMsgBoxShow,
loading: false,
loadingTips: '任务正在准备中,喝杯水回来再看看~',
};
},
components: { TaskName, ModelBaseDatasetSelect, ResourceSpecification, RunParameters, LoadingMask },
methods: {
submitClick() {
const r1 = this.$refs.taskNameRef.check();
const r2 = this.$refs.modelBaseDatasetSelectRef.check();
const r3 = this.$refs.resourceSpecificationRef.check();
const r4 = this.$refs.runParametersRef.check();
if (r1 && r2 && r3 && r4) {
const subData = {
userName: this.userName,
type: 0, // 盘古2.6模型
model_size: this.modelSize,
display_job_name: this.$refs.taskNameRef.taskName,
description: this.descr.trim(),
};

const specData = this.$refs.resourceSpecificationRef.getSpecData();
subData.spec_id = Number(specData.id);

const parameters = this.$refs['runParametersRef'].getParameter();
const parameterObj = {
parameter: parameters.map((item) => { return { ...item } }),
};
subData.run_para_list = JSON.stringify(parameterObj);

const datasetTab = this.$refs.modelBaseDatasetSelectRef.tabIndex;
if (datasetTab == 1) { // 本地上传
this.loading = true;
this.$refs.modelBaseDatasetSelectRef.localUpload().then(res => {
console.log(res);
if (res) {
if (res.status == '1') {
this.loading = false;
this.$message({
type: 'error',
message: res.msg,
});
} else if (res.status == '0') {
if (res.total == 1) {
const file = res.files[0];
if (res.total == res.success) { // 上传完成
subData.attachment = file.uuid;
subData.dataset_name = file.name;
this.submit(subData);
return;
}
if (file.uploaded == '1') { // 文件已经上传过
subData.attachment = file.uuid;
subData.dataset_name = file._fileName;
this.submit(subData);
return;
}
this.loading = false;
this.$message({
type: 'error',
message: '本地数据集文件上传失败',
});
} else {
this.loading = false;
this.$message({
type: 'error',
message: '本地数据集文件上传失败',
});
}
}
} else {
this.loading = false;
this.$message({
type: 'error',
message: '本地数据集文件上传失败',
});
}
}).catch(err => {
console.log(err);
this.loading = false;
this.$message({
type: 'error',
message: '本地数据集文件上传失败',
});
});
} else {
if (datasetTab == 0) {
subData.sample_dataset_type = this.$refs.modelBaseDatasetSelectRef.sampleDataset;
} else if (datasetTab == 2) {
const datasetList = this.$refs.modelBaseDatasetSelectRef.getPlatformDataset();
subData.attachment = datasetList[0].id;
subData.dataset_name = datasetList[0].name;
}
this.submit(subData);
}
}
},
submit(data) {
this.loading = true;
setFinetuneCreate(data).then(res => {
res = res.data;
if (res && res.code == 0) {
window.location.href = '/extension/modelbase/pangufinetune';
} else {
this.loading = false;
this.$message({
type: 'error',
duration: 4000,
message: res.message,
});
}
}).catch(err => {
this.loading = false;
console.log(err);
this.$message({
type: 'error',
message: '创建任务失败',
});
});
},
cancel() {
window.location.href = `/extension/modelbase/pangufinetune`;
}
},
beforeMount() {
const urlParams = getUrlSearchParams();
const type = urlParams.type;
if (type == '0' || type == '1' || type == '2' || type == '3') {
this.exampleDatasetType = Number(type);
if (type == '0') {
this.datasetTab = 1;
}
}
const metaEl = document.querySelectorAll('meta[name="_uid"]');
if (!metaEl.length) {
window.location.href = `/user/login?redirect_to=${encodeURIComponent(window.location.href)}`;
return;
} else {
const uid = metaEl[0].getAttribute('content');
const uname = metaEl[0].getAttribute('content-ext');
this.userName = uname;
}
getCheckRepo().then(res => {
res = res.data;
if (res.code == 0) {
this.datasetId = res.message;
} else {
this.$message({
type: 'error',
message: res.message,
});
}
}).catch(err => {
console.log(err);
});
getPointAccountInfo().then(res => {
const data = res.data;
this.pointShow = data.cloudBrainPaySwitch ? true : false;
this.pointBlance = data.pointAccount ? data.pointAccount.balance : 0;
}).catch(err => {
console.log(err);
});
getSpecInfo({ userName: this.userName }).then(res => {
const data = res.data || [];
this.specData = data;
if (data.length) {
this.spec = data[0].id.toString();
}
}).catch(err => {
console.log(err);
});
},
mounted() { },
beforeDestroy() { },
};
</script>

<style scoped lang="less">
.area {
width: 1050px !important;
margin-top: 40px;

.area-title {
height: 45px;
border-color: rgb(212, 212, 213);
border-width: 1px;
border-style: solid;
border-radius: 5px 5px 0px 0px;
font-size: 14px;
background: rgb(240, 240, 240);
line-height: 45px;
padding-left: 15px;
font-weight: 550;
font-size: 16px;
color: rgb(16, 16, 16);
}

.err-msg-box-already {
margin: 1em 0;
padding: 1em 1.5em;
background-color: rgba(242, 113, 28, 0.05);
border: 1px solid rgba(242, 113, 28, 1);
border-radius: 5px;

.msg-content {
display: flex;
align-items: center;

i {
font-size: 35px;
color: rgba(242, 113, 28, 1);
}

.msg-content-tip {
text-align: left;
margin-left: 1rem;

.line-1 {
font-weight: 600;
line-height: 2;

span {
color: rgba(242, 113, 28, 1);
}
}

.line-2 {
color: #939393
}
}
}
}

.area-content {
border-color: rgb(212, 212, 213);
border-width: 1px;
border-style: solid;
margin-top: -1px;

.content {
padding: 20px 180px 24px 0;

.main-title {
font-weight: 550;
font-size: 16px;
color: rgb(16, 16, 16);
height: 22px;
margin: 30px 0 30px 110px;
}

.row {
display: flex;
margin-bottom: 28px;

.row-title {
width: 200px;
text-align: right;
margin-right: 24px;
color: #101010;
font-size: 14px;
display: flex;
justify-content: flex-end;
padding-top: 6px;

.required {
position: relative;

&::after {
position: absolute;
content: "*";
top: -3px;
right: -10px;
color: red;
}
}
}

.row-content {
flex: 1;
}
}
}
}
}

.btn {
color: rgb(2, 0, 4);
background-color: rgb(194, 199, 204);
border-color: rgb(194, 199, 204);

&.confirm-btn {
color: #fff;
background-color: rgb(56, 158, 13);
border-color: rgb(56, 158, 13);

&.is-disabled {
opacity: .45 !important;
}
}
}

.tips-c {
color: #888888;
font-size: 14px;
margin-top: 10px;
margin-bottom: -6px;
margin-left: 173px;
text-align: center;

p {
span {
color: #f2711c;
}
}
}
</style>

+ 17
- 0
web_src/vuepages/pages/modelbase/create/vp-modelbase-create.js View File

@@ -0,0 +1,17 @@
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import localeEn from 'element-ui/lib/locale/lang/en';
import localeZh from 'element-ui/lib/locale/lang/zh-CN';
import { i18n, lang } from '~/langs';
import App from './index.vue';

Vue.use(ElementUI, {
locale: lang === 'zh-CN' ? localeZh : localeEn,
size: 'small',
});

new Vue({
i18n,
render: (h) => h(App),
}).$mount('#__vue-root');

+ 448
- 0
web_src/vuepages/pages/modelbase/home/index.vue View File

@@ -0,0 +1,448 @@
<template>
<div>
<TopHeader>
<div class="title">
<div class="title-1">大模型基地</div>
<!-- <div class="title-2">
<a target="_blank" href="">
<span>中国NLP大模型总部基地启动仪式</span>
<i class="el-icon-video-play"></i>
</a>
</div> -->
</div>
</TopHeader>
<div>
<div class="model-category-c" :class="index % 2 == 0 ? 'model-category-c-bg' : ''" v-for="(item, index) in list"
:key="index">
<div class="model-category ui container">
<div class="model-sum">
<div class="model-sum-head">
<div v-if="item.logo" class="model-sum-logo">
<img :src="item.logo" />
</div>
<div class="model-sum-title">{{ item.name }}</div>
</div>
<div class="model-sum-descr">{{ item.descr }}</div>
<div class="model-sum-op">
<a v-if="item.repoUrl" target="_blank" :href="item.repoUrl">项目主页</a>
<a v-if="item.homeUrl" target="_blank" :href="item.homeUrl">项目官网</a>
</div>
</div>
<div class="model-list">
<div class="model-item" v-for="(_item, _index) in item.models" :key="index + '-' + _index">
<div class="model-title">{{ _item.name }}</div>
<div class="model-descr" :title="_item.descr">{{ _item.descr }}</div>
<div class="model-op" :class="_item.class ? _item.class : ''">
<div class="model-op-btn model-op-coming-soon" v-if="_item.comingSoon">
<i class="el-icon-stopwatch"></i>
<span>敬请期待</span>
</div>
<a class="model-op-btn model-op-experience-now" v-if="_item.experienceNow" target="_blank"
:href="_item.experienceUrl">
<span>立即体验</span>
<i class="el-icon-right"></i>
</a>
<a class="model-op-btn model-op-can-use" v-if="_item.canUse" :href="_item.useUrl">
<span>定制个性化大模型</span>
<i class="el-icon-right"></i>
</a>
<a class="model-op-btn model-op-download" v-if="_item.download">
<span>立即下载</span>
<i class="el-icon-download"></i>
</a>
<a class="model-op-btn model-op-download" v-if="_item.detail" target="_blank" :href="_item.detailUrl">
<span>详情</span>
<i class="el-icon-right"></i>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="model-category-c" :class="list.length % 2 == 0 ? 'model-category-c-bg' : ''">
<div class="ui container model-category-more">
<div class="more-model">更多大模型接入中...</div>
</div>
</div>
</div>
</div>
</template>

<script>
import TopHeader from '../components/TopHeader.vue';

export default {
data() {
return {
list: [{
name: '鹏程·盘古大模型',
logo: 'https://openi.pcl.ac.cn/repo-avatars/34812-90a3a6045581fa11522237766a4db6b3',
descr: '以中文为核心的鹏程·盘古大语言模型,更强的任务理解与处理能力。',
homeUrl: '',
models: [{
name: '鹏程·盘古',
descr: '业界首个2000亿参数以中文为核心的预训练生成语言模型。',
canUse: true,
useUrl: '/extension/modelbase/pangufinetune',
comingSoon: false,
experienceNow: false,
experienceUrl: 'https://pangu-alpha.pcl.ac.cn/index_params3_usingJiebaPrint_addLoginTimeout_hu.html',
download: false,
// class: 'justify-content-space-between',
}, {
name: '鹏程·盘古多任务增强版',
descr: '以中文为核心的鹏程·盘古大语言模型,更强的任务理解与处理能力。',
canUse: false,
comingSoon: true,
experienceNow: false,
experienceUrl: 'https://pangu-alpha.pcl.ac.cn/index_params3_usingJiebaPrint_addLoginTimeout_pangu_upgrade.html',
download: false,
}, {
name: '鹏程·盘古多语言',
descr: '2.6B预训练多语言模型+2.6B一带一路53种语言机器翻译模型,单模型支持53种语言任意两种语言间的互译。',
canUse: false,
comingSoon: true,
experienceNow: false,
download: false,
}, {
name: '鹏程·盘古对话',
descr: '大规模开放域对话生成模型,可控、可靠可信、有智慧的自然人机对话模型。',
canUse: false,
comingSoon: true,
experienceNow: false,
download: false,
}]
}, {
name: '文心大模型',
logo: '',
descr: '文心大模型,AI应用场景全覆盖',
homeUrl: 'https://wenxin.baidu.com/',
models: [{
name: 'ERNIE-ViLG 2.0',
descr: '全球规模最大的中文跨模态生成模型,可通过自然语言实现图像生成与编辑,由鹏城云脑Ⅱ提供算力支持 。',
canUse: false,
comingSoon: false,
experienceNow: true,
experienceUrl: 'https://openi.pcl.ac.cn/extension/wenxin',
download: false,
}, {
name: '鹏城-百度·文心',
descr: '全球首个知识增强超大模型,参数规模2600亿,在60多项典型任务中取得了世界领先效果,在各类AI应用场景中均具备极强的泛化能力。',
canUse: false,
comingSoon: false,
experienceNow: false,
download: false,
detail: true,
detailUrl: 'https://openi.pcl.ac.cn/PCLNLP/ernie3.0_torch',
}, {
name: 'ERNIE 3.0-xbase-zh',
descr: '280M参数重量级通用模型。',
canUse: false,
comingSoon: false,
experienceNow: false,
download: false,
detail: true,
detailUrl: 'https://openi.pcl.ac.cn/FoundationModel/ERNIE-3.0',
}]
}, {
name: 'CLIP模型',
logo: '',
descr: 'CLIP模型的中文版本,使用大规模中文数据进行训练,可用于图文检索和图像、文本的表征提取,应用于搜索、推荐等应用场景',
repoUrl: 'https://openi.pcl.ac.cn/FoundationModel/Chinese-CLIP',
models: [{
name: 'Chinese-CLIP ViT-L/14@336px',
descr: 'CLIP模型的中文版本,使用大规模中文数据进行训练(~2亿图文对),旨在帮助用户快速实现中文领域的图文特征&相似度计算、跨模态检索、零样本图片分类等任务。',
canUse: false,
comingSoon: false,
experienceNow: false,
download: false,
detail: true,
detailUrl: 'https://openi.pcl.ac.cn/FoundationModel/Chinese-CLIP/modelmanage/model_readme_tmpl?name=CN-CLIP_ViT-L%2F14%40336px',
}, {
name: 'Chinese-CLIP_ViT-H/14',
descr: 'CLIP模型的中文版本,使用大规模中文数据进行训练(~2亿图文对),旨在帮助用户快速实现中文领域的图文特征&相似度计算、跨模态检索、零样本图片分类等任务。',
canUse: false,
comingSoon: false,
experienceNow: false,
download: false,
detail: true,
detailUrl: 'https://openi.pcl.ac.cn/FoundationModel/Chinese-CLIP/modelmanage/model_readme_tmpl?name=Chinese-CLIP_ViT-H%2F14',
}, {
name: 'Chinese-CLIP_ViT-L/14',
descr: 'CLIP模型的中文版本,使用大规模中文数据进行训练(~2亿图文对),旨在帮助用户快速实现中文领域的图文特征&相似度计算、跨模态检索、零样本图片分类等任务。',
canUse: false,
comingSoon: false,
experienceNow: false,
download: false,
detail: true,
detailUrl: 'https://openi.pcl.ac.cn/FoundationModel/Chinese-CLIP/modelmanage/model_readme_tmpl?name=Chinese-CLIP_ViT-L%2F14',
}]
}, {
name: '悟道大模型',
logo: '',
descr: '由清华大学和北京智源提供的系列开源模型成果',
reopUrl: 'https://openi.pcl.ac.cn/BAAI/WuDao-Model',
homeUrl: 'https://flagopen.baai.ac.cn/#/home',
models: [{
name: 'CogView',
descr: 'CogView参数量为40亿,模型可实现文本生成图像,经过微调后可实现国画、油画、水彩画、轮廓画等图像生成。目前在公认MS COCO文生图任务上取得了超过OpenAI DALL·E的成绩,获得世界第一。',
canUse: false,
comingSoon: false,
experienceNow: false,
download: false,
detail: true,
detailUrl: 'https://openi.pcl.ac.cn/FoundationModel/CogView',
}, {
name: 'GLM',
descr: 'GLM是以英文为核心的预训练语言模型系列,基于新的预训练范式实现单一模型在语言理解和生成任务方面取得了最佳结果,并且超过了在相同数据量进行训练的常见预训练模型(例如BERT,RoBERTa和T5),目前已开源1.1亿、3.35亿、4.10亿、5.15亿、100亿参数规模的模型。',
canUse: false,
comingSoon: false,
experienceNow: false,
download: false,
detail: true,
detailUrl: 'https://openi.pcl.ac.cn/FoundationModel/GLM/',
}, {
name: 'Transformer-XL',
descr: 'Transformer-XL是以中文为核心的预训练语言生成模型,参数规模为29亿,目前可支持包括文章生成、智能作诗、评论/摘要生成等主流NLG任务。',
canUse: false,
comingSoon: false,
experienceNow: false,
download: false,
detail: true,
detailUrl: 'https://openi.pcl.ac.cn/FoundationModel/Chinese-Transformer-XL',
}, {
name: 'EVA',
descr: 'EVA是一个开放领域的中文对话预训练模型,是目前最大的汉语对话模型,参数量达到28亿,并且在包括不同领域14亿汉语的悟道对话数据集(WDC)上进行预训练。',
canUse: false,
comingSoon: false,
experienceNow: false,
download: false,
detail: true,
detailUrl: 'https://openi.pcl.ac.cn/FoundationModel/EVA',
}, {
name: 'Lawformer',
descr: 'Lawformer是世界首创法律领域长文本中文预训练模型,参数规模达到1亿。',
canUse: false,
comingSoon: false,
experienceNow: false,
download: false,
detail: true,
detailUrl: 'https://openi.pcl.ac.cn/FoundationModel/LegalPLMs',
}]
}, {
name: '封神榜模型',
logo: '',
descr: '中文语言预训练模型',
homeUrl: 'https://fengshenbang-doc.readthedocs.io/zh/latest/index.html',
models: [{
name: '二郎神系列',
descr: '处理理解任务,拥有开源时最大的中文bert模型,2021登顶FewCLUE和ZeroCLUE。',
canUse: false,
comingSoon: false,
experienceNow: false,
download: false,
detail: true,
detailUrl: 'https://openi.pcl.ac.cn/FoundationModel/Erlangshen-MegatronBert-1.3B/modelmanage/show_model',
}, {
name: '太乙系列',
descr: '太乙系列模型主要应用于跨模态场景,包括文本图像生成,蛋白质结构预测, 语音-文本表示等。',
canUse: false,
comingSoon: false,
experienceNow: false,
download: false,
detail: true,
detailUrl: 'https://openi.pcl.ac.cn/FoundationModel/taiyi/modelmanage/show_model',
}, {
name: '闻仲系列',
descr: '专注于生成任务,提供了多个不同参数量的生成模型,例如GPT2等。',
canUse: false,
comingSoon: false,
experienceNow: false,
download: false,
detail: true,
detailUrl: 'https://openi.pcl.ac.cn/FoundationModel/Wenzhong/modelmanage/show_model',
}]
}]
};
},
components: { TopHeader, },
methods: {},
beforeMount() { },
mounted() { },
beforeDestroy() { },
};
</script>

<style scoped lang="less">
.title {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
// margin-top: -10px;

.title-1 {
font-weight: 400;
font-size: 28px;
color: rgb(16, 16, 16);
height: 42px;
}

.title-2 {
font-weight: 400;
font-size: 14px;

a {
color: rgba(16, 16, 16, 1);
}
}
}

.model-category-c {
display: flex;
align-items: center;
padding: 65px 0;

.model-category {
display: flex;
align-items: center;

.model-sum {
width: 320px;
margin-right: 20px;
align-self: baseline;
margin-top: 20px;
padding-left: 40px;

.model-sum-head {
display: flex;
align-items: center;

.model-sum-logo {
height: 38px;
width: 38px;
margin-right: 10px;

img {
width: 100%;
height: 100%;
}
}

.model-sum-title {
flex: 1;
font-size: 20px;
color: rgb(16, 16, 16);
}
}

.model-sum-descr {
font-weight: 300;
font-size: 14px;
color: rgb(136, 136, 136);
margin: 10px 0 16px 0;
}

.model-sum-op {
a {
font-size: 14px;
color: rgb(50, 145, 248);
font-weight: 500;
margin-right: 10px;
}
}
}

.model-list {
flex: 1;
display: flex;
flex-wrap: wrap;

.model-item {
width: 318px;
margin: 10px 10px 16px 10px;
border-color: rgba(157, 197, 226, 0.4);
border-width: 1px;
border-style: solid;
box-shadow: rgba(157, 197, 226, 0.2) 0px 5px 10px 0px;
color: rgb(16, 16, 16);
border-radius: 6px;
padding: 24px 16px;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736766e-17%2C%201%2C%20-0.2531545429373838%2C%206.123233995736766e-17%2C%200.5%2C%200)%22%3E%3Cstop%20stop-color%3D%22%23eef2ff%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%221%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");

.model-title {
font-weight: 510;
font-size: 16px;
color: rgb(16, 16, 16);
text-align: center;
}

.model-descr {
font-weight: 300;
font-size: 12px;
color: rgb(136, 136, 136);
margin: 10px 0 14px 0;
height: 60px;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}

.model-op {
display: flex;
justify-content: center;
align-items: center;

.model-op-coming-soon {
font-size: 14px;
color: rgba(136, 136, 136, 1);
}

.model-op-can-use,
.model-op-experience-now,
.model-op-download {
color: rgb(50, 145, 248);
}

&.justify-content-space-between {
justify-content: space-between;
}
}
}
}
}

&.model-category-c-bg {
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(0.11899999999999993%2C%201.217%2C%20-0.0901857098765432%2C%200.11899999999999993%2C%200.269%2C%20-0.22)%22%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.47%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23e5e7eb%22%20stop-opacity%3D%220.3%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}
}

.model-category-more {
display: flex;
justify-content: center;
align-items: center;

.more-model {
width: 367px;
height: 60px;
border-color: rgba(16, 16, 16, 0.1);
border-width: 1px;
border-style: solid;
border-radius: 10px;
font-size: 14px;
padding: 0px;
text-align: center;
line-height: 20px;
font-weight: normal;
font-style: normal;
background: rgba(208, 231, 255, 0.3);
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

+ 17
- 0
web_src/vuepages/pages/modelbase/home/vp-modelbase-home.js View File

@@ -0,0 +1,17 @@
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import localeEn from 'element-ui/lib/locale/lang/en';
import localeZh from 'element-ui/lib/locale/lang/zh-CN';
import { i18n, lang } from '~/langs';
import App from './index.vue';

Vue.use(ElementUI, {
locale: lang === 'zh-CN' ? localeZh : localeEn,
size: 'small',
});

new Vue({
i18n,
render: (h) => h(App),
}).$mount('#__vue-root');

+ 265
- 0
web_src/vuepages/pages/modelbase/inference/index.vue View File

@@ -0,0 +1,265 @@
<template>
<div class="ui container">
<div class="top-header"><span>模型部署体验</span></div>
<div class="content">
<div class="content-left">
<div class="header">
<div class="tips1">请输入测试内容:</div>
<div class="tips2" v-show="jobCategory == 1 || jobCategory == 2 || jobCategory == 3">* 请参考示例格式输入</div>
</div>
<div class="text-input-c">
<el-input class="text-input" type="textarea" v-model="inputTxt" :rows="9"></el-input>
</div>
<div class="generate-btn-c">
<el-button type="primary" size="default" @click="generate" :loading="generating">
{{ generating ? '生成中' : '开始生成' }}
</el-button>
</div>
</div>
<div class="content-right">
<div class="header">
<div class="tips1">生成内容:</div>
</div>
<div class="text-output-c">
<el-input class="text-output" type="textarea" v-model="outputTxt" readonly :rows="9"></el-input>
</div>
</div>
</div>
<div class="footer-tips-c">
<div class="tips">* 部署的模型可以体验30分钟,超时后将关闭。</div>
<div class="tips">* 本项目处于前沿探索阶段,体验功能仅供学术测试使用。请勿输入违反法律内容,同时,未经许可,禁止分享,传播输入及生成文本内容。感谢理解!</div>
</div>
</div>
</template>

<script>
import { getFinetuneServiceStatus, getFinetuneServiceInference } from '~/apis/modules/modelbase';
import { getUrlSearchParams } from '~/utils';

// 文本分类
const sample1 = [{
q: '对文本进行分类:\n有没有新疆旅游攻略?\n选项:游戏,财经,旅游,农业\n答案:',
a: '旅游',
}];

// 中英翻译
const sample2 = [{
q: '请将下面这段话改写成英文:\n本组织的目标:确保所有工作人员,包括特派团人员,以健康的身体履行职责,并促进和维持工作人员的健康\n答案:',
a: 'Objective of the Organization: To ensure that all staff members, including those on mission, are fit to carry out their duties, and to promote and maintain the health of staff'
}];

// 情感分类
const sample3 = [{
q: '情感分类:\n这个比精装的只少了一个大盒子和苏打志(写真书),价格只要一半,还是比较划算的,内容都是一样的精彩~\n选项:积极,消极\n答案:',
a: '积极'
}];

// 开放问答示例
// const sample3 = [{
// q: '问题:力的单位是什么?\n答案:',
// a: '牛顿',
// }, {
// q: '问题:2012年奥运在哪里举行?\n答案:',
// a: '伦敦',
// }, {
// q: '问题:郑州是那个省的\n答案:',
// a: '河南',
// }, {
// q: '问题:山东,山西,这里的山指\n答案:',
// a: '太行山',
// }, {
// q: '问题:北宋是中国历史上以汉族为主体建立的封建王朝,建都在哪里?\n答案:',
// a: '开封',
// }, {
// q: '问题:赌城在哪(美国)?\n答案:',
// a: '拉斯维加斯',
// }];

const randomSample = (sampleList) => {
return { ...sampleList[Math.floor(Math.random() * sampleList.length)] };
};

export default {
data() {
return {
userName: '',
repoName: 'openi-notebook',

jobId: '',
type: '',
jobCategory: '',
inputTxt: '',
outputTxt: '',

generating: false,
printTimer: null,
};
},
components: {},
methods: {
generateSample() {
if (this.jobCategory == 1) {
const sample = randomSample(sample1);
this.inputTxt = sample.q;
// this.outputTxt = sample.a;
this.printText(sample.a);
} else if (this.jobCategory == 2) {
const sample = randomSample(sample2);
this.inputTxt = sample.q;
// this.outputTxt = sample.a;
this.printText(sample.a);
} else if (this.jobCategory == 3) {
const sample = randomSample(sample3);
this.inputTxt = sample.q;
// this.outputTxt = sample.a;
this.printText(sample.a);
} else if (this.jobCategory == 0) {

}
},
generate() {
const inputTxt = this.inputTxt.trim();
if (!inputTxt) {
this.$message({
type: 'info',
message: '请先输入测试内容',
});
return;
}
this.generating = true;
getFinetuneServiceInference({
userName: this.userName,
jobId: this.jobId,
text: inputTxt
}).then(res => {
this.generating = false;
const data = res.data;
if (data && data.text != undefined) {
// this.outputTxt = data.text;
this.printText(data.text);
}
}).catch(err => {
this.generating = false;
console.log(err);
});
},
printText(text) {
this.printTimer && clearInterval(this.printTimer);
let len = text.length;
this.printTimer = setInterval(() => {
if (len < 0) clearInterval(this.printTimer);
this.outputTxt = text.slice(0, text.length - (len - 1));
len--;
}, 18);
}
},
beforeMount() {
const metaEl = document.querySelectorAll('meta[name="_uid"]');
if (!metaEl.length) { // 未登录
window.location.href = `/user/login?redirect_to=${encodeURIComponent(window.location.href)}`;
return;
}
const uid = metaEl[0].getAttribute('content');
const uname = metaEl[0].getAttribute('content-ext');
this.userName = uname;
const urlParams = getUrlSearchParams();
this.jobId = urlParams.jobid;
this.type = urlParams.type;
this.jobCategory = urlParams.jobcategory;
console.log(this.jobId, this.type, this.jobCategory);
this.generateSample();
},
mounted() { },
beforeDestroy() { },
};
</script>

<style scoped lang="less">
.top-header {
font-weight: 400;
font-size: 28px;
color: rgb(16, 16, 16);
text-align: center;
margin: 40px 0;
height: 30px;
line-height: 30px;
}

.content {
display: flex;
flex-wrap: wrap;

.content-left {
flex: 1;
margin: 0px 18px;
min-width: 400px;

.header {
display: flex;
justify-content: space-between;
margin-bottom: 8px;

.tips1 {
color: rgba(136, 136, 136, 1);
}

.tips2 {
color: rgb(250, 140, 22);
}
}

.text-input-c {
.text-input {
/deep/ .el-textarea__inner {
font-size: 16px;
color: rgb(16, 16, 16);
}
}
}

.generate-btn-c {
text-align: right;
margin-top: 20px;
}
}

.content-right {
flex: 1;
margin: 0px 18px;
min-width: 400px;

.header {
display: flex;
margin-bottom: 8px;

.tips1 {
color: rgba(136, 136, 136, 1);
}
}

.text-output-c {
.text-output {
/deep/ .el-textarea__inner {
font-size: 16px;
color: rgb(16, 16, 16);
background-color: rgba(229, 231, 235, 0.1);

&:focus {
border-color: #DCDFE6;
}
}
}
}
}
}

.footer-tips-c {
margin-top: 40px;
color: rgb(250, 140, 22);
padding: 18px;

.tips {
margin-bottom: 5px;
}
}
</style>

+ 17
- 0
web_src/vuepages/pages/modelbase/inference/vp-modelbase-inference.js View File

@@ -0,0 +1,17 @@
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import localeEn from 'element-ui/lib/locale/lang/en';
import localeZh from 'element-ui/lib/locale/lang/zh-CN';
import { i18n, lang } from '~/langs';
import App from './index.vue';

Vue.use(ElementUI, {
locale: lang === 'zh-CN' ? localeZh : localeEn,
size: 'small',
});

new Vue({
i18n,
render: (h) => h(App),
}).$mount('#__vue-root');

+ 510
- 0
web_src/vuepages/pages/modelbase/model/index.vue View File

@@ -0,0 +1,510 @@
<template>
<div>
<TopHeader>
<div class="title">
<div class="title-1">鹏程·盘古大模型</div>
<div class="title-2">以中文为核心的鹏程·盘古大语言模型,更强的任务理解与处理能力。</div>
<div class="title-3">
<a target="_blank" href="https://openi.pcl.ac.cn/PCL-Platform.Intelligence/pcl_pangu">项目主页</a>
</div>
</div>
</TopHeader>
<div class="type-list-container">
<div class="ui container">
<div class="content-tips">
<span>* 社区提供了三个示例场景,用户可以使用提供的示例数据集新建微调任务,也可以上传自己的数据集,定制符合自己的应用场景的鹏程·盘古大模型。</span>
<a target="_blank"
href="https://openi.pcl.ac.cn/PCL-Platform.Intelligence/pcl_pangu/src/branch/master/docs/README_DATASET.md">如何构造数据集</a>
</div>
<div class="type-list">
<div class="item" :class="item.class ? item.class : ''" v-for="(item, index) in list" :key="index">
<div class="item-title">{{ item.name }}</div>
<div class="item-descr" :title="item.descr">{{ item.descr }}</div>
<div class="item-op">
<a class="item-op-btn" :href="item.href">
<span>新建任务</span>
<i class="el-icon-right"></i>
</a>
</div>
</div>
</div>
</div>
</div>
<div>
<div class="ui container">
<div class="table-container">
<el-table ref="tableRef" border :data="tableData" style="width:100%;" v-loading="loading" stripe>
<el-table-column label="任务名称" align="center" header-align="center">
<template slot-scope="scope">
<a :href="`/${userName}/${repoName}/modelarts/train-job/${scope.row.job_id}`" target="_blank"> {{
scope.row.display_job_name }}</a>
</template>
</el-table-column>
<el-table-column label="任务场景" prop="jobCategoryStr" align="center" header-align="center"></el-table-column>
<el-table-column label="训练状态" prop="status" align="center" header-align="center">
<template slot-scope="scope">
<div class="job-status-c">
<i class="job-status-icon" :class="scope.row.status"></i>
<span>{{ scope.row.status }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="部署状态" align="center" header-align="center">
<template slot-scope="scope">
<div class="job-status-c">
<i class="job-status-icon"
:class="[scope.row.deploy_status, deployStateMap[scope.row.deploy_status]]"></i>
<span>{{ scope.row.deploy_status }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createdTimeStr" align="center" header-align="center"></el-table-column>
<el-table-column label="操作" align="center" header-align="center" width="220">
<template slot-scope="scope">
<div class="op-btn-c">
<a class="btn" href="javascript:;" v-if="scope.row.canInference"
@click="startInference(scope.row)">体验</a>
<a class="btn" href="javascript:;" v-if="scope.row.canReDeploy"
@click="updateDeploy(scope.row, 'running')">启动</a>
<a class="btn" href="javascript:;" v-if="scope.row.canStopTrain" @click="stopTrain(scope.row)">停止</a>
<a class="btn" href="javascript:;" v-if="scope.row.canStopDeploy"
@click="updateDeploy(scope.row, 'stopped')">停止</a>
<a class="btn" href="javascript:;" v-if="scope.row.canDeploy" @click="deploy(scope.row)">立即部署</a>
<a class="btn" href="javascript:;" v-if="scope.row.canDelete" @click="deleteTask(scope.row)">删除</a>
</div>
</template>
</el-table-column>
</el-table>
</div>
<div class="table-tips">* 最多创建 5 条任务</div>
</div>
</div>
<LoadingMask :loading="maskLoading" :tips="maskLoadingTips"></LoadingMask>
</div>
</template>

<script>
import dayjs from 'dayjs';
import TopHeader from '../components/TopHeader.vue';
import LoadingMask from '../components/cloudbrain/LoadingMask.vue';
import { getFinetuneList, getJobStatus, stopTrainJob, deleteTrainJob, setFinetuneService, getFinetuneServiceStatus, updateFinetuneService } from '~/apis/modules/modelbase';
import { getListValueWithKey } from '~/utils';

const TrainJobFinalState = [
'STOPPED', 'CREATE_FAILED', 'UNAVAILABLE', 'DELETED', 'RESIZE_FAILED', 'SUCCEEDED', 'IMAGE_FAILED', 'SUBMIT_FAILED', 'DELETE_FAILED',
'KILLED', 'COMPLETED', 'FAILED', 'CANCELED', 'LOST', 'START_FAILED', 'SUBMIT_MODEL_FAILED', 'DEPLOY_SERVICE_FAILED', 'CHECK_FAILED',
];

const DeployStateMap = {
'BUILDING': 'WAITING',
'WAITING': 'WAITING',
'DEPLOYING': 'RUNNING',
'SUCCEEDED': 'SUCCEEDED',
'STOP': 'STOPPED',
'FAIL': 'FAILED',
}

export default {
data() {
return {
deployStateMap: DeployStateMap,
userName: '',
repoName: 'openi-notebook',
list: [{
_name: '文本分类',
name: '文本分类',
descr: '根据文本分类选项对文本内容进行自动分类,每段文本内容均可属于某一种选项类别。',
key: 1,
href: '/extension/modelbase/pangufinetune/create?type=1',
}, {
_name: '中英翻译',
name: '中英翻译',
descr: '指定翻译的目标语言,将源语言翻译成目标语言。支持中译英、英译中。',
key: 2,
href: '/extension/modelbase/pangufinetune/create?type=2',
}, {
_name: '情感分类',
name: '情感分类',
descr: '输入一段文本,对其情感进行分类,判断其属于积极的或者消极的。',
key: 3,
href: '/extension/modelbase/pangufinetune/create?type=3',
}, {
_name: '自定义',
name: '自定义更多场景',
descr: '支持用户自定义数据模板,微调以适用于更多业务场景,例如摘要生成、对对联、阅读理解等。',
key: 0,
class: 'item-green',
href: '/extension/modelbase/pangufinetune/create?type=0',
}],
loading: false,
tableData: [],
maxJobNum: '-',

refreshTimer: null,
isOperating: false,

maskLoading: false,
maskLoadingTips: '',
};
},
components: { TopHeader, LoadingMask },
methods: {
getTableData() {
if (!this.userName) return;
this.loading = true;
this.refreshTimer && clearInterval(this.refreshTimer);
getFinetuneList({ userName: this.userName }).then(res => {
res = res.data;
this.loading = false;
this.maxJobNum = res.maxJobNum || this.maxJobNum;
const data = res.fineTuneJobs;
this.tableData = data.map((item) => {
const taskJob = {
...item,
jobCategoryStr: getListValueWithKey(this.list, item.job_category, 'key', '_name'),
createdTimeStr: dayjs(item.created_unix * 1000).format('YYYY-MM-DD HH:mm:ss'),
}
this.checkOperation(taskJob);
return taskJob;
});
this.startRefreshTaskTimer();
}).catch(err => {
console.log(err);
this.loading = false;
});
},
checkOperation(taskJob) {
taskJob.canStopTrain = ['RUNNING', 'WAITING'].includes(taskJob.status);
taskJob.canDelete = ['STOPPED', 'FAILED', 'START_FAILED', 'KILLED', 'COMPLETED', 'SUCCEEDED', 'CREATE_FAILED'].includes(taskJob.status)
&& ['', 'STOP', 'FAILED'].includes(taskJob.deploy_status);
taskJob.canDeploy = ['SUCCEEDED', 'COMPLETED'].includes(taskJob.status) && !['BUILDING', 'WAITING', 'DEPLOYING', 'SUCCEEDED', 'STOP', 'FAILED'].includes(taskJob.deploy_status);
taskJob.canReDeploy = ['SUCCEEDED', 'COMPLETED'].includes(taskJob.status) && ['STOP'].includes(taskJob.deploy_status);
taskJob.canStopDeploy = ['SUCCEEDED', 'COMPLETED'].includes(taskJob.status) && ['DEPLOYING', 'SUCCEEDED'].includes(taskJob.deploy_status);
taskJob.canInference = ['SUCCEEDED', 'COMPLETED'].includes(taskJob.status) && ['SUCCEEDED'].includes(taskJob.deploy_status);
},
startRefreshTaskTimer() {
this.refreshTimer = setInterval(() => {
for (let i = 0, iLen = this.tableData.length; i < iLen; i++) {
const taskJob = this.tableData[i];
this.refreshTaskStatus(taskJob);
}
}, 6 * 1000);
},
refreshTaskStatus(taskJob, notCheck) {
if (notCheck || !TrainJobFinalState.includes(taskJob.status)) {
getJobStatus({
userName: this.userName,
jobId: taskJob.job_id,
}).then(res => {
const data = res.data || {};
taskJob.status = data.JobStatus;
taskJob.start_time = data.StartTime;
taskJob.ai_center = data.AiCenter;
taskJob.job_duration = data.JobDuration;
this.checkOperation(taskJob);
}).catch(err => {
console.log(err);
});
}
if (notCheck || ['BUILDING', 'WAITING', 'DEPLOYING', 'SUCCEEDED'].includes(taskJob.deploy_status)) {
getFinetuneServiceStatus({
userName: this.userName,
jobId: taskJob.job_id,
}).then(res => {
const data = res.data;
taskJob.deploy_status = data.fineTuneDeployStatus;
this.checkOperation(taskJob);
}).catch(err => {
console.log(err);
});
}
},
stopTrain(taskJob) {
if (this.isOperating) return;
this.isOperating = true;
this.maskLoadingTips = '正在停止,请稍后';
this.maskLoading = true;
stopTrainJob({
userName: this.userName,
jobId: taskJob.job_id,
}).then(res => {
this.isOperating = false;
this.maskLoading = false;
this.refreshTaskStatus(taskJob, true);
setTimeout(() => {
this.checkOperation(taskJob);
}, 200);
}).catch(err => {
this.isOperating = false;
this.maskLoading = false;
console.log(err);
this.$message({
type: 'error',
message: '操作失败',
});
})
},
deleteTask(taskJob) {
if (this.isOperating) return;
this.$confirm(`您确认删除该任务么?此任务一旦删除不可恢复。`, '提示', {
confirmButtonText: '确定操作',
cancelButtonText: '取消操作',
type: 'warning',
}).then(async () => {
this.isOperating = true;
this.maskLoadingTips = '任务删除中,请稍后';
this.maskLoading = true;
deleteTrainJob({
userName: this.userName,
jobId: taskJob.job_id,
}).then(res => {
this.isOperating = false;
this.maskLoading = false;
this.getTableData();
}).catch(err => {
this.isOperating = false;
this.maskLoading = false;
console.log(err);
this.$message({
type: 'error',
message: '操作失败',
});
})
}).catch(() => { });
},
deploy(taskJob) {
if (this.isOperating) return;
this.isOperating = true;
this.maskLoadingTips = '正在部署,请稍后';
this.maskLoading = true;
setFinetuneService({
userName: this.userName,
jobId: taskJob.job_id,
}).then(res => {
this.isOperating = false;
this.maskLoading = false;
res = res.data;
if (res.code != 0) {
this.$message({
type: 'error',
message: res.message,
});
}
this.refreshTaskStatus(taskJob, true);
}).catch(err => {
console.log(err);
this.isOperating = false;
this.maskLoading = false;
this.$message({
type: 'error',
message: '操作失败',
});
})
},
updateDeploy(taskJob, type) { // type=stop,running
if (this.isOperating) return;
this.isOperating = true;
this.maskLoadingTips = '正在操作,请稍后';
this.maskLoading = true;
updateFinetuneService({
userName: this.userName,
jobId: taskJob.job_id,
status: type,
}).then(res => {
this.isOperating = false;
this.maskLoading = false;
res = res.data;
if (res.code != 0) {
this.$message({
type: 'error',
message: res.message,
});
}
this.refreshTaskStatus(taskJob, true);
}).catch(err => {
console.log(err);
this.isOperating = false;
this.maskLoading = false;
this.$message({
type: 'error',
message: '操作失败',
});
});
},
startInference(taskJob) {
if (this.isOperating) return;
window.open(`/extension/modelbase/pangufinetune/inference?jobid=${taskJob.job_id}&type=${taskJob.type}&jobcategory=${taskJob.job_category}`);
},
},
beforeMount() {
const metaEl = document.querySelectorAll('meta[name="_uid"]');
if (metaEl.length) {
const uid = metaEl[0].getAttribute('content');
const uname = metaEl[0].getAttribute('content-ext');
this.userName = uname;
this.getTableData();
}
},
mounted() { },
beforeDestroy() {
this.refreshTimer && clearInterval(this.refreshTimer);
},
};
</script>

<style scoped lang="less">
.title {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
margin-top: -10px;

.title-1 {
font-weight: 400;
font-size: 28px;
color: rgb(16, 16, 16);
height: 42px;
}

.title-2 {
font-weight: 400;
font-size: 14px;
color: rgba(16, 16, 16, 1);
margin: 12px 0 10px;
}

.title-3 {
font-weight: 400;
font-size: 14px;

a {
color: rgba(3, 102, 214, 1);
text-decoration: underline;
}
}
}

.content-tips {
font-size: 14px;
color: rgb(136, 136, 136);
padding-left: 10px;

a {
font-size: 14px;
color: rgba(3, 102, 214, 1);
text-decoration: underline;
}
}

.type-list-container {
padding: 60px 0 60px 0;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(0.11899999999999993%2C%201.217%2C%20-0.0901857098765432%2C%200.11899999999999993%2C%200.269%2C%20-0.22)%22%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.47%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23e5e7eb%22%20stop-opacity%3D%220.3%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");

.type-list {
display: flex;
justify-content: center;

.item {
width: 318px;
height: 160px;
border-color: rgba(157, 197, 226, 0.4);
border-width: 1px;
border-style: solid;
box-shadow: rgba(157, 197, 226, 0.2) 0px 5px 10px 0px;
color: rgb(16, 16, 16);
border-radius: 6px;
margin: 20px 10px;
padding: 20px 15px;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736766e-17%2C%201%2C%20-0.2531545429373838%2C%206.123233995736766e-17%2C%200.5%2C%200)%22%3E%3Cstop%20stop-color%3D%22%23eef2ff%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%221%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");

.item-title {
text-align: center;
font-weight: 500;
font-size: 16px;
color: rgb(16, 16, 16);
}

.item-descr {
font-weight: 300;
font-size: 12px;
color: rgb(136, 136, 136);
margin: 10px 0px;
height: 60px;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}

.item-op {
display: flex;
align-items: center;
justify-content: center;

.item-op-btn {
font-size: 14px;
color: rgb(50, 145, 248);
}
}

&.item-green {
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736766e-17%2C%201%2C%20-0.2531545429373838%2C%206.123233995736766e-17%2C%200.5%2C%200)%22%3E%3Cstop%20stop-color%3D%22%23cffff0%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%221%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}
}
}
}

.table-container {
margin-top: 40px;

/deep/ .el-table__header {
th {
background: rgb(249, 249, 249);
font-size: 12px;
color: rgb(136, 136, 136);
font-weight: normal;
}
}

/deep/ .el-table__body {
td {
font-size: 12px;
}
}

/deep/ .el-radio__label {
display: none;
}

.job-status-c {
display: flex;
align-items: center;
padding-left: 10px;

.job-status-icon {
margin-right: 4px;
}
}

.op-btn-c {
display: flex;
align-items: center;
justify-content: center;

.btn {
margin: 0 4px;
}
}
}

.table-tips {
margin-top: 10px;
font-weight: 300;
font-size: 14px;
color: rgba(250, 140, 22, 1);
}
</style>

+ 17
- 0
web_src/vuepages/pages/modelbase/model/vp-modelbase-model.js View File

@@ -0,0 +1,17 @@
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import localeEn from 'element-ui/lib/locale/lang/en';
import localeZh from 'element-ui/lib/locale/lang/zh-CN';
import { i18n, lang } from '~/langs';
import App from './index.vue';

Vue.use(ElementUI, {
locale: lang === 'zh-CN' ? localeZh : localeEn,
size: 'small',
});

new Vue({
i18n,
render: (h) => h(App),
}).$mount('#__vue-root');

Loading…
Cancel
Save