From 3435df2caaebe39441c622c982c63bfe3eb92dbf Mon Sep 17 00:00:00 2001 From: yokowu <18836617@qq.com> Date: Wed, 30 Jul 2025 16:10:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(model):=20=E6=A8=A1=E5=9E=8B=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=AB=98=E7=BA=A7=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/cmd/server/wire_gen.go | 4 +- backend/config/config.json.tmpl | 13 +- backend/db/migrate/schema.go | 3 +- backend/db/model.go | 17 + backend/db/model/model.go | 3 + backend/db/model/where.go | 10 + backend/db/model_create.go | 71 +++++ backend/db/model_update.go | 37 +++ backend/db/mutation.go | 75 ++++- backend/db/runtime/runtime.go | 4 +- backend/docs/swagger.json | 83 +++++ backend/domain/model.go | 35 ++- backend/ent/schema/model.go | 2 + backend/ent/types/types.go | 20 ++ backend/go.mod | 2 +- backend/go.sum | 4 +- backend/internal/model/repo/model.go | 19 +- backend/internal/model/usecase/model.go | 16 + backend/internal/openai/usecase/openai.go | 62 ++-- ui/src/api/Cli.ts | 40 +++ ui/src/api/WorkspaceFile.ts | 312 ++++++++++++++++++ ui/src/api/index.ts | 2 + ui/src/api/types.ts | 315 ++++++++++++++++++- ui/src/pages/model/components/modelModal.tsx | 289 +++++++++++++++++ 24 files changed, 1377 insertions(+), 61 deletions(-) create mode 100644 ui/src/api/Cli.ts create mode 100644 ui/src/api/WorkspaceFile.ts diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go index 0a8fe9e..35567b7 100644 --- a/backend/cmd/server/wire_gen.go +++ b/backend/cmd/server/wire_gen.go @@ -1,6 +1,6 @@ // Code generated by Wire. DO NOT EDIT. -//go:generate go run -mod=mod github.com/google/wire/cmd/wire +//go:generate go run github.com/google/wire/cmd/wire //go:build !wireinject // +build !wireinject @@ -69,7 +69,7 @@ func newServer() (*Server, error) { proxyUsecase := usecase.NewProxyUsecase(proxyRepo, modelRepo, slogLogger) llmProxy := proxy.NewLLMProxy(slogLogger, configConfig, proxyUsecase) openAIRepo := repo3.NewOpenAIRepo(client) - openAIUsecase := openai.NewOpenAIUsecase(configConfig, openAIRepo, slogLogger) + openAIUsecase := openai.NewOpenAIUsecase(configConfig, openAIRepo, modelRepo, slogLogger) extensionRepo := repo4.NewExtensionRepo(client) extensionUsecase := usecase2.NewExtensionUsecase(extensionRepo, configConfig, slogLogger) proxyMiddleware := middleware.NewProxyMiddleware(proxyUsecase) diff --git a/backend/config/config.json.tmpl b/backend/config/config.json.tmpl index f15d7e1..bb7b23c 100644 --- a/backend/config/config.json.tmpl +++ b/backend/config/config.json.tmpl @@ -5,15 +5,16 @@ "default": { "apiProvider": "openai", "apiModelId": "{{ .chatModel }}", - "openAiBaseUrl": "{{ .apiBase}}/v1", + "openAiBaseUrl": "{{ .apiBase }}/v1", "openAiApiKey": "{{ .apikey }}", "openAiModelId": "{{ .chatModel }}", + "openAiR1FormatEnabled": {{ .r1Enabled }}, "openAiCustomModelInfo": { - "maxTokens": 8192, - "contextWindow": 65536, - "supportsImages": false, - "supportsComputerUse": false, - "supportsPromptCache": false + "maxTokens": {{ .maxTokens }}, + "contextWindow": {{ .contextWindow }}, + "supportsImages": {{ .supportsImages }}, + "supportsComputerUse": {{ .supportsComputerUse }}, + "supportsPromptCache": {{ .supportsPromptCache }} }, "id": "59admorkig4" } diff --git a/backend/db/migrate/schema.go b/backend/db/migrate/schema.go index 65549ef..2a967a5 100644 --- a/backend/db/migrate/schema.go +++ b/backend/db/migrate/schema.go @@ -249,6 +249,7 @@ var ( {Name: "is_internal", Type: field.TypeBool, Default: false}, {Name: "provider", Type: field.TypeString}, {Name: "status", Type: field.TypeString, Default: "active"}, + {Name: "parameters", Type: field.TypeJSON, Nullable: true}, {Name: "context_length", Type: field.TypeInt, Nullable: true}, {Name: "created_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime}, @@ -262,7 +263,7 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "models_users_models", - Columns: []*schema.Column{ModelsColumns[15]}, + Columns: []*schema.Column{ModelsColumns[16]}, RefColumns: []*schema.Column{UsersColumns[0]}, OnDelete: schema.SetNull, }, diff --git a/backend/db/model.go b/backend/db/model.go index d142555..5cbc816 100644 --- a/backend/db/model.go +++ b/backend/db/model.go @@ -3,6 +3,7 @@ package db import ( + "encoding/json" "fmt" "strings" "time" @@ -12,6 +13,7 @@ import ( "github.com/chaitin/MonkeyCode/backend/consts" "github.com/chaitin/MonkeyCode/backend/db/model" "github.com/chaitin/MonkeyCode/backend/db/user" + "github.com/chaitin/MonkeyCode/backend/ent/types" "github.com/google/uuid" ) @@ -44,6 +46,8 @@ type Model struct { Provider consts.ModelProvider `json:"provider,omitempty"` // Status holds the value of the "status" field. Status consts.ModelStatus `json:"status,omitempty"` + // Parameters holds the value of the "parameters" field. + Parameters *types.ModelParam `json:"parameters,omitempty"` // ContextLength holds the value of the "context_length" field. ContextLength int `json:"context_length,omitempty"` // CreatedAt holds the value of the "created_at" field. @@ -92,6 +96,8 @@ func (*Model) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { + case model.FieldParameters: + values[i] = new([]byte) case model.FieldIsInternal: values[i] = new(sql.NullBool) case model.FieldContextLength: @@ -195,6 +201,14 @@ func (m *Model) assignValues(columns []string, values []any) error { } else if value.Valid { m.Status = consts.ModelStatus(value.String) } + case model.FieldParameters: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field parameters", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &m.Parameters); err != nil { + return fmt.Errorf("unmarshal field parameters: %w", err) + } + } case model.FieldContextLength: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field context_length", values[i]) @@ -295,6 +309,9 @@ func (m *Model) String() string { builder.WriteString("status=") builder.WriteString(fmt.Sprintf("%v", m.Status)) builder.WriteString(", ") + builder.WriteString("parameters=") + builder.WriteString(fmt.Sprintf("%v", m.Parameters)) + builder.WriteString(", ") builder.WriteString("context_length=") builder.WriteString(fmt.Sprintf("%v", m.ContextLength)) builder.WriteString(", ") diff --git a/backend/db/model/model.go b/backend/db/model/model.go index e5f1943..70666d3 100644 --- a/backend/db/model/model.go +++ b/backend/db/model/model.go @@ -39,6 +39,8 @@ const ( FieldProvider = "provider" // FieldStatus holds the string denoting the status field in the database. FieldStatus = "status" + // FieldParameters holds the string denoting the parameters field in the database. + FieldParameters = "parameters" // FieldContextLength holds the string denoting the context_length field in the database. FieldContextLength = "context_length" // FieldCreatedAt holds the string denoting the created_at field in the database. @@ -82,6 +84,7 @@ var Columns = []string{ FieldIsInternal, FieldProvider, FieldStatus, + FieldParameters, FieldContextLength, FieldCreatedAt, FieldUpdatedAt, diff --git a/backend/db/model/where.go b/backend/db/model/where.go index 8e0c4b0..08137e2 100644 --- a/backend/db/model/where.go +++ b/backend/db/model/where.go @@ -922,6 +922,16 @@ func StatusContainsFold(v consts.ModelStatus) predicate.Model { return predicate.Model(sql.FieldContainsFold(FieldStatus, vc)) } +// ParametersIsNil applies the IsNil predicate on the "parameters" field. +func ParametersIsNil() predicate.Model { + return predicate.Model(sql.FieldIsNull(FieldParameters)) +} + +// ParametersNotNil applies the NotNil predicate on the "parameters" field. +func ParametersNotNil() predicate.Model { + return predicate.Model(sql.FieldNotNull(FieldParameters)) +} + // ContextLengthEQ applies the EQ predicate on the "context_length" field. func ContextLengthEQ(v int) predicate.Model { return predicate.Model(sql.FieldEQ(FieldContextLength, v)) diff --git a/backend/db/model_create.go b/backend/db/model_create.go index ca959d3..1368f5b 100644 --- a/backend/db/model_create.go +++ b/backend/db/model_create.go @@ -16,6 +16,7 @@ import ( "github.com/chaitin/MonkeyCode/backend/db/model" "github.com/chaitin/MonkeyCode/backend/db/task" "github.com/chaitin/MonkeyCode/backend/db/user" + "github.com/chaitin/MonkeyCode/backend/ent/types" "github.com/google/uuid" ) @@ -155,6 +156,12 @@ func (mc *ModelCreate) SetNillableStatus(cs *consts.ModelStatus) *ModelCreate { return mc } +// SetParameters sets the "parameters" field. +func (mc *ModelCreate) SetParameters(tp *types.ModelParam) *ModelCreate { + mc.mutation.SetParameters(tp) + return mc +} + // SetContextLength sets the "context_length" field. func (mc *ModelCreate) SetContextLength(i int) *ModelCreate { mc.mutation.SetContextLength(i) @@ -385,6 +392,10 @@ func (mc *ModelCreate) createSpec() (*Model, *sqlgraph.CreateSpec) { _spec.SetField(model.FieldStatus, field.TypeString, value) _node.Status = value } + if value, ok := mc.mutation.Parameters(); ok { + _spec.SetField(model.FieldParameters, field.TypeJSON, value) + _node.Parameters = value + } if value, ok := mc.mutation.ContextLength(); ok { _spec.SetField(model.FieldContextLength, field.TypeInt, value) _node.ContextLength = value @@ -656,6 +667,24 @@ func (u *ModelUpsert) UpdateStatus() *ModelUpsert { return u } +// SetParameters sets the "parameters" field. +func (u *ModelUpsert) SetParameters(v *types.ModelParam) *ModelUpsert { + u.Set(model.FieldParameters, v) + return u +} + +// UpdateParameters sets the "parameters" field to the value that was provided on create. +func (u *ModelUpsert) UpdateParameters() *ModelUpsert { + u.SetExcluded(model.FieldParameters) + return u +} + +// ClearParameters clears the value of the "parameters" field. +func (u *ModelUpsert) ClearParameters() *ModelUpsert { + u.SetNull(model.FieldParameters) + return u +} + // SetContextLength sets the "context_length" field. func (u *ModelUpsert) SetContextLength(v int) *ModelUpsert { u.Set(model.FieldContextLength, v) @@ -955,6 +984,27 @@ func (u *ModelUpsertOne) UpdateStatus() *ModelUpsertOne { }) } +// SetParameters sets the "parameters" field. +func (u *ModelUpsertOne) SetParameters(v *types.ModelParam) *ModelUpsertOne { + return u.Update(func(s *ModelUpsert) { + s.SetParameters(v) + }) +} + +// UpdateParameters sets the "parameters" field to the value that was provided on create. +func (u *ModelUpsertOne) UpdateParameters() *ModelUpsertOne { + return u.Update(func(s *ModelUpsert) { + s.UpdateParameters() + }) +} + +// ClearParameters clears the value of the "parameters" field. +func (u *ModelUpsertOne) ClearParameters() *ModelUpsertOne { + return u.Update(func(s *ModelUpsert) { + s.ClearParameters() + }) +} + // SetContextLength sets the "context_length" field. func (u *ModelUpsertOne) SetContextLength(v int) *ModelUpsertOne { return u.Update(func(s *ModelUpsert) { @@ -1429,6 +1479,27 @@ func (u *ModelUpsertBulk) UpdateStatus() *ModelUpsertBulk { }) } +// SetParameters sets the "parameters" field. +func (u *ModelUpsertBulk) SetParameters(v *types.ModelParam) *ModelUpsertBulk { + return u.Update(func(s *ModelUpsert) { + s.SetParameters(v) + }) +} + +// UpdateParameters sets the "parameters" field to the value that was provided on create. +func (u *ModelUpsertBulk) UpdateParameters() *ModelUpsertBulk { + return u.Update(func(s *ModelUpsert) { + s.UpdateParameters() + }) +} + +// ClearParameters clears the value of the "parameters" field. +func (u *ModelUpsertBulk) ClearParameters() *ModelUpsertBulk { + return u.Update(func(s *ModelUpsert) { + s.ClearParameters() + }) +} + // SetContextLength sets the "context_length" field. func (u *ModelUpsertBulk) SetContextLength(v int) *ModelUpsertBulk { return u.Update(func(s *ModelUpsert) { diff --git a/backend/db/model_update.go b/backend/db/model_update.go index 7c0d227..300c6b3 100644 --- a/backend/db/model_update.go +++ b/backend/db/model_update.go @@ -16,6 +16,7 @@ import ( "github.com/chaitin/MonkeyCode/backend/db/predicate" "github.com/chaitin/MonkeyCode/backend/db/task" "github.com/chaitin/MonkeyCode/backend/db/user" + "github.com/chaitin/MonkeyCode/backend/ent/types" "github.com/google/uuid" ) @@ -231,6 +232,18 @@ func (mu *ModelUpdate) SetNillableStatus(cs *consts.ModelStatus) *ModelUpdate { return mu } +// SetParameters sets the "parameters" field. +func (mu *ModelUpdate) SetParameters(tp *types.ModelParam) *ModelUpdate { + mu.mutation.SetParameters(tp) + return mu +} + +// ClearParameters clears the value of the "parameters" field. +func (mu *ModelUpdate) ClearParameters() *ModelUpdate { + mu.mutation.ClearParameters() + return mu +} + // SetContextLength sets the "context_length" field. func (mu *ModelUpdate) SetContextLength(i int) *ModelUpdate { mu.mutation.ResetContextLength() @@ -426,6 +439,12 @@ func (mu *ModelUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := mu.mutation.Status(); ok { _spec.SetField(model.FieldStatus, field.TypeString, value) } + if value, ok := mu.mutation.Parameters(); ok { + _spec.SetField(model.FieldParameters, field.TypeJSON, value) + } + if mu.mutation.ParametersCleared() { + _spec.ClearField(model.FieldParameters, field.TypeJSON) + } if value, ok := mu.mutation.ContextLength(); ok { _spec.SetField(model.FieldContextLength, field.TypeInt, value) } @@ -735,6 +754,18 @@ func (muo *ModelUpdateOne) SetNillableStatus(cs *consts.ModelStatus) *ModelUpdat return muo } +// SetParameters sets the "parameters" field. +func (muo *ModelUpdateOne) SetParameters(tp *types.ModelParam) *ModelUpdateOne { + muo.mutation.SetParameters(tp) + return muo +} + +// ClearParameters clears the value of the "parameters" field. +func (muo *ModelUpdateOne) ClearParameters() *ModelUpdateOne { + muo.mutation.ClearParameters() + return muo +} + // SetContextLength sets the "context_length" field. func (muo *ModelUpdateOne) SetContextLength(i int) *ModelUpdateOne { muo.mutation.ResetContextLength() @@ -960,6 +991,12 @@ func (muo *ModelUpdateOne) sqlSave(ctx context.Context) (_node *Model, err error if value, ok := muo.mutation.Status(); ok { _spec.SetField(model.FieldStatus, field.TypeString, value) } + if value, ok := muo.mutation.Parameters(); ok { + _spec.SetField(model.FieldParameters, field.TypeJSON, value) + } + if muo.mutation.ParametersCleared() { + _spec.ClearField(model.FieldParameters, field.TypeJSON) + } if value, ok := muo.mutation.ContextLength(); ok { _spec.SetField(model.FieldContextLength, field.TypeInt, value) } diff --git a/backend/db/mutation.go b/backend/db/mutation.go index f177ad8..6dc87b4 100644 --- a/backend/db/mutation.go +++ b/backend/db/mutation.go @@ -8213,6 +8213,7 @@ type ModelMutation struct { is_internal *bool provider *consts.ModelProvider status *consts.ModelStatus + parameters **types.ModelParam context_length *int addcontext_length *int created_at *time.Time @@ -8829,6 +8830,55 @@ func (m *ModelMutation) ResetStatus() { m.status = nil } +// SetParameters sets the "parameters" field. +func (m *ModelMutation) SetParameters(tp *types.ModelParam) { + m.parameters = &tp +} + +// Parameters returns the value of the "parameters" field in the mutation. +func (m *ModelMutation) Parameters() (r *types.ModelParam, exists bool) { + v := m.parameters + if v == nil { + return + } + return *v, true +} + +// OldParameters returns the old "parameters" field's value of the Model entity. +// If the Model object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ModelMutation) OldParameters(ctx context.Context) (v *types.ModelParam, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldParameters is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldParameters requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldParameters: %w", err) + } + return oldValue.Parameters, nil +} + +// ClearParameters clears the value of the "parameters" field. +func (m *ModelMutation) ClearParameters() { + m.parameters = nil + m.clearedFields[model.FieldParameters] = struct{}{} +} + +// ParametersCleared returns if the "parameters" field was cleared in this mutation. +func (m *ModelMutation) ParametersCleared() bool { + _, ok := m.clearedFields[model.FieldParameters] + return ok +} + +// ResetParameters resets all changes to the "parameters" field. +func (m *ModelMutation) ResetParameters() { + m.parameters = nil + delete(m.clearedFields, model.FieldParameters) +} + // SetContextLength sets the "context_length" field. func (m *ModelMutation) SetContextLength(i int) { m.context_length = &i @@ -9086,7 +9136,7 @@ func (m *ModelMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *ModelMutation) Fields() []string { - fields := make([]string, 0, 15) + fields := make([]string, 0, 16) if m.user != nil { fields = append(fields, model.FieldUserID) } @@ -9123,6 +9173,9 @@ func (m *ModelMutation) Fields() []string { if m.status != nil { fields = append(fields, model.FieldStatus) } + if m.parameters != nil { + fields = append(fields, model.FieldParameters) + } if m.context_length != nil { fields = append(fields, model.FieldContextLength) } @@ -9164,6 +9217,8 @@ func (m *ModelMutation) Field(name string) (ent.Value, bool) { return m.Provider() case model.FieldStatus: return m.Status() + case model.FieldParameters: + return m.Parameters() case model.FieldContextLength: return m.ContextLength() case model.FieldCreatedAt: @@ -9203,6 +9258,8 @@ func (m *ModelMutation) OldField(ctx context.Context, name string) (ent.Value, e return m.OldProvider(ctx) case model.FieldStatus: return m.OldStatus(ctx) + case model.FieldParameters: + return m.OldParameters(ctx) case model.FieldContextLength: return m.OldContextLength(ctx) case model.FieldCreatedAt: @@ -9302,6 +9359,13 @@ func (m *ModelMutation) SetField(name string, value ent.Value) error { } m.SetStatus(v) return nil + case model.FieldParameters: + v, ok := value.(*types.ModelParam) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetParameters(v) + return nil case model.FieldContextLength: v, ok := value.(int) if !ok { @@ -9383,6 +9447,9 @@ func (m *ModelMutation) ClearedFields() []string { if m.FieldCleared(model.FieldDescription) { fields = append(fields, model.FieldDescription) } + if m.FieldCleared(model.FieldParameters) { + fields = append(fields, model.FieldParameters) + } if m.FieldCleared(model.FieldContextLength) { fields = append(fields, model.FieldContextLength) } @@ -9415,6 +9482,9 @@ func (m *ModelMutation) ClearField(name string) error { case model.FieldDescription: m.ClearDescription() return nil + case model.FieldParameters: + m.ClearParameters() + return nil case model.FieldContextLength: m.ClearContextLength() return nil @@ -9462,6 +9532,9 @@ func (m *ModelMutation) ResetField(name string) error { case model.FieldStatus: m.ResetStatus() return nil + case model.FieldParameters: + m.ResetParameters() + return nil case model.FieldContextLength: m.ResetContextLength() return nil diff --git a/backend/db/runtime/runtime.go b/backend/db/runtime/runtime.go index a173257..0042397 100644 --- a/backend/db/runtime/runtime.go +++ b/backend/db/runtime/runtime.go @@ -167,11 +167,11 @@ func init() { // model.DefaultStatus holds the default value on creation for the status field. model.DefaultStatus = consts.ModelStatus(modelDescStatus.Default.(string)) // modelDescCreatedAt is the schema descriptor for created_at field. - modelDescCreatedAt := modelFields[14].Descriptor() + modelDescCreatedAt := modelFields[15].Descriptor() // model.DefaultCreatedAt holds the default value on creation for the created_at field. model.DefaultCreatedAt = modelDescCreatedAt.Default.(func() time.Time) // modelDescUpdatedAt is the schema descriptor for updated_at field. - modelDescUpdatedAt := modelFields[15].Descriptor() + modelDescUpdatedAt := modelFields[16].Descriptor() // model.DefaultUpdatedAt holds the default value on creation for the updated_at field. model.DefaultUpdatedAt = modelDescUpdatedAt.Default.(func() time.Time) // model.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 4423de1..7ee6972 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -161,13 +161,17 @@ "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 1, "description": "分页", "name": "page", "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 10, "description": "每页多少条记录", "name": "size", "in": "query" @@ -264,13 +268,17 @@ "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 1, "description": "分页", "name": "page", "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 10, "description": "每页多少条记录", "name": "size", "in": "query" @@ -524,13 +532,17 @@ "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 1, "description": "分页", "name": "page", "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 10, "description": "每页多少条记录", "name": "size", "in": "query" @@ -649,13 +661,17 @@ "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 1, "description": "分页", "name": "page", "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 10, "description": "每页多少条记录", "name": "size", "in": "query" @@ -1693,13 +1709,17 @@ "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 1, "description": "分页", "name": "page", "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 10, "description": "每页多少条记录", "name": "size", "in": "query" @@ -1830,13 +1850,17 @@ "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 1, "description": "分页", "name": "page", "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 10, "description": "每页多少条记录", "name": "size", "in": "query" @@ -2166,13 +2190,17 @@ "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 1, "description": "分页", "name": "page", "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 10, "description": "每页多少条记录", "name": "size", "in": "query" @@ -2269,13 +2297,17 @@ "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 1, "description": "分页", "name": "page", "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 10, "description": "每页多少条记录", "name": "size", "in": "query" @@ -2681,7 +2713,9 @@ "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 1, "description": "分页", "name": "page", "in": "query" @@ -2693,7 +2727,9 @@ "in": "query" }, { + "minimum": 1, "type": "integer", + "default": 10, "description": "每页多少条记录", "name": "size", "in": "query" @@ -3984,6 +4020,14 @@ } ] }, + "param": { + "description": "高级参数", + "allOf": [ + { + "$ref": "#/definitions/domain.ModelParam" + } + ] + }, "provider": { "description": "提供商", "enum": [ @@ -4604,6 +4648,14 @@ "description": "输出token数", "type": "integer" }, + "param": { + "description": "高级参数", + "allOf": [ + { + "$ref": "#/definitions/domain.ModelParam" + } + ] + }, "provider": { "description": "提供商", "allOf": [ @@ -4713,6 +4765,29 @@ } } }, + "domain.ModelParam": { + "type": "object", + "properties": { + "context_window": { + "type": "integer" + }, + "max_tokens": { + "type": "integer" + }, + "r1_enabled": { + "type": "boolean" + }, + "support_computer_use": { + "type": "boolean" + }, + "support_images": { + "type": "boolean" + }, + "support_prompt_cache": { + "type": "boolean" + } + } + }, "domain.ModelTokenUsage": { "type": "object", "properties": { @@ -5138,6 +5213,14 @@ "description": "模型名称", "type": "string" }, + "param": { + "description": "高级参数", + "allOf": [ + { + "$ref": "#/definitions/domain.ModelParam" + } + ] + }, "provider": { "description": "提供商", "enum": [ diff --git a/backend/domain/model.go b/backend/domain/model.go index 6ed6e82..728153e 100644 --- a/backend/domain/model.go +++ b/backend/domain/model.go @@ -124,6 +124,27 @@ type CreateModelReq struct { APIVersion string `json:"api_version"` APIHeader string `json:"api_header"` ModelType consts.ModelType `json:"model_type"` // 模型类型 llm:对话模型 coder:代码模型 + Param *ModelParam `json:"param"` // 高级参数 +} + +type ModelParam struct { + R1Enabled bool `json:"r1_enabled"` + MaxTokens int `json:"max_tokens"` + ContextWindow int `json:"context_window"` + SupprtImages bool `json:"support_images"` + SupportComputerUse bool `json:"support_computer_use"` + SupportPromptCache bool `json:"support_prompt_cache"` +} + +func DefaultModelParam() *ModelParam { + return &ModelParam{ + R1Enabled: false, + MaxTokens: 8192, + ContextWindow: 64000, + SupprtImages: false, + SupportComputerUse: false, + SupportPromptCache: false, + } } type UpdateModelReq struct { @@ -135,7 +156,8 @@ type UpdateModelReq struct { APIKey *string `json:"api_key"` // 接口密钥 如:sk-xxxx APIVersion *string `json:"api_version"` APIHeader *string `json:"api_header"` - Status *consts.ModelStatus `json:"status"` // 状态 active:启用 inactive:禁用 + Status *consts.ModelStatus `json:"status"` // 状态 active:启用 inactive:禁用 + Param *ModelParam `json:"param,omitempty"` // 高级参数 } type ModelTokenUsageResp struct { @@ -176,6 +198,7 @@ type Model struct { IsActive bool `json:"is_active"` // 是否启用 Input int64 `json:"input"` // 输入token数 Output int64 `json:"output"` // 输出token数 + Param ModelParam `json:"param"` // 高级参数 IsInternal bool `json:"is_internal"` // 是否内部模型 CreatedAt int64 `json:"created_at"` // 创建时间 UpdatedAt int64 `json:"updated_at"` // 更新时间 @@ -198,6 +221,16 @@ func (m *Model) From(e *db.Model) *Model { m.Status = e.Status m.IsInternal = e.IsInternal m.IsActive = e.Status == consts.ModelStatusActive + if p := e.Parameters; p != nil { + m.Param = ModelParam{ + R1Enabled: p.R1Enabled, + MaxTokens: p.MaxTokens, + ContextWindow: p.ContextWindow, + SupprtImages: p.SupprtImages, + SupportComputerUse: p.SupportComputerUse, + SupportPromptCache: p.SupportPromptCache, + } + } m.CreatedAt = e.CreatedAt.Unix() m.UpdatedAt = e.UpdatedAt.Unix() diff --git a/backend/ent/schema/model.go b/backend/ent/schema/model.go index 1baa804..4e639a0 100644 --- a/backend/ent/schema/model.go +++ b/backend/ent/schema/model.go @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/chaitin/MonkeyCode/backend/consts" + "github.com/chaitin/MonkeyCode/backend/ent/types" ) // Model holds the schema definition for the Model entity. @@ -42,6 +43,7 @@ func (Model) Fields() []ent.Field { field.Bool("is_internal").Default(false), field.String("provider").GoType(consts.ModelProvider("")), field.String("status").GoType(consts.ModelStatus("")).Default(string(consts.ModelStatusActive)), + field.JSON("parameters", &types.ModelParam{}).Optional(), field.Int("context_length").Optional(), field.Time("created_at").Default(time.Now), field.Time("updated_at").Default(time.Now).UpdateDefault(time.Now), diff --git a/backend/ent/types/types.go b/backend/ent/types/types.go index 06f6cd5..bc743ed 100644 --- a/backend/ent/types/types.go +++ b/backend/ent/types/types.go @@ -19,3 +19,23 @@ type CustomOAuth struct { AvatarField string `json:"avatar_field"` // 用户信息回包中的头像URL字段名` EmailField string `json:"email_field"` // 用户信息回包中的邮箱字段名 } + +type ModelParam struct { + R1Enabled bool `json:"r1_enabled"` + MaxTokens int `json:"max_tokens"` + ContextWindow int `json:"context_window"` + SupprtImages bool `json:"support_images"` + SupportComputerUse bool `json:"support_computer_use"` + SupportPromptCache bool `json:"support_prompt_cache"` +} + +func DefaultModelParam() *ModelParam { + return &ModelParam{ + R1Enabled: false, + MaxTokens: 8192, + ContextWindow: 64000, + SupprtImages: false, + SupportComputerUse: false, + SupportPromptCache: false, + } +} diff --git a/backend/go.mod b/backend/go.mod index f354304..318b7e6 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -4,7 +4,7 @@ go 1.23.7 require ( entgo.io/ent v0.14.4 - github.com/GoYoko/web v1.3.0 + github.com/GoYoko/web v1.4.0 github.com/cloudwego/eino v0.3.51 github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250710065240-482d48888f25 github.com/doquangtan/socket.io/v4 v4.0.8 diff --git a/backend/go.sum b/backend/go.sum index 1b82297..df917d5 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -8,8 +8,8 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/GoYoko/web v1.3.0 h1:f59n1xkyEUjy/wF0ilX86oJWpyQrJJQQzGkRIve1YJw= -github.com/GoYoko/web v1.3.0/go.mod h1:DL9/gvuUG2jcBE1XUIY+9QBrrhdshzPEdxMCzR9jUHo= +github.com/GoYoko/web v1.4.0 h1:DUe5ZsUA3i5HWAX2HiUmkuNop+xEzPYpZpg2ozUw37E= +github.com/GoYoko/web v1.4.0/go.mod h1:DL9/gvuUG2jcBE1XUIY+9QBrrhdshzPEdxMCzR9jUHo= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= diff --git a/backend/internal/model/repo/model.go b/backend/internal/model/repo/model.go index f203b70..12435c0 100644 --- a/backend/internal/model/repo/model.go +++ b/backend/internal/model/repo/model.go @@ -15,6 +15,7 @@ import ( "github.com/chaitin/MonkeyCode/backend/db/model" "github.com/chaitin/MonkeyCode/backend/db/task" "github.com/chaitin/MonkeyCode/backend/domain" + "github.com/chaitin/MonkeyCode/backend/ent/types" "github.com/chaitin/MonkeyCode/backend/pkg/cvt" "github.com/chaitin/MonkeyCode/backend/pkg/entx" ) @@ -62,7 +63,7 @@ func (r *ModelRepo) Create(ctx context.Context, m *domain.CreateModelReq) (*db.M } r.cache.Delete(string(m.ModelType)) - return r.db.Model.Create(). + create := r.db.Model.Create(). SetUserID(uid). SetShowName(m.ShowName). SetModelName(m.ModelName). @@ -72,8 +73,20 @@ func (r *ModelRepo) Create(ctx context.Context, m *domain.CreateModelReq) (*db.M SetAPIVersion(m.APIVersion). SetAPIHeader(m.APIHeader). SetModelType(m.ModelType). - SetStatus(status). - Save(ctx) + SetStatus(status) + if m.Param != nil { + create.SetParameters(&types.ModelParam{ + R1Enabled: m.Param.R1Enabled, + MaxTokens: m.Param.MaxTokens, + ContextWindow: m.Param.ContextWindow, + SupprtImages: m.Param.SupprtImages, + SupportComputerUse: m.Param.SupportComputerUse, + SupportPromptCache: m.Param.SupportPromptCache, + }) + } else { + create.SetParameters(types.DefaultModelParam()) + } + return create.Save(ctx) } func (r *ModelRepo) Update(ctx context.Context, id string, fn func(tx *db.Tx, old *db.Model, up *db.ModelUpdateOne) error) (*db.Model, error) { diff --git a/backend/internal/model/usecase/model.go b/backend/internal/model/usecase/model.go index 076c59d..968706b 100644 --- a/backend/internal/model/usecase/model.go +++ b/backend/internal/model/usecase/model.go @@ -21,6 +21,7 @@ import ( "github.com/chaitin/MonkeyCode/backend/db" "github.com/chaitin/MonkeyCode/backend/db/model" "github.com/chaitin/MonkeyCode/backend/domain" + "github.com/chaitin/MonkeyCode/backend/ent/types" "github.com/chaitin/MonkeyCode/backend/pkg/cvt" "github.com/chaitin/MonkeyCode/backend/pkg/request" ) @@ -206,6 +207,7 @@ func (m *ModelUsecase) GetTokenUsage(ctx context.Context, modelType consts.Model // Update implements domain.ModelUsecase. func (m *ModelUsecase) Update(ctx context.Context, req *domain.UpdateModelReq) (*domain.Model, error) { + m.logger.With("req", req).With("param", req.Param).DebugContext(ctx, "update model") model, err := m.repo.Update(ctx, req.ID, func(tx *db.Tx, old *db.Model, up *db.ModelUpdateOne) error { if req.ModelName != nil { up.SetModelName(*req.ModelName) @@ -239,6 +241,16 @@ func (m *ModelUsecase) Update(ctx context.Context, req *domain.UpdateModelReq) ( } up.SetStatus(*req.Status) } + if req.Param != nil { + up.SetParameters(&types.ModelParam{ + R1Enabled: req.Param.R1Enabled, + MaxTokens: req.Param.MaxTokens, + ContextWindow: req.Param.ContextWindow, + SupprtImages: req.Param.SupprtImages, + SupportComputerUse: req.Param.SupportComputerUse, + SupportPromptCache: req.Param.SupportPromptCache, + }) + } return nil }) if err != nil { @@ -262,6 +274,9 @@ func (m *ModelUsecase) getQuery(req *domain.GetProviderModelListReq) request.Que } q["type"] = "text" q["sub_type"] = string(req.Type) + if req.Type == consts.ModelTypeLLM { + q["sub_type"] = "chat" + } // 硅基流动不支持coder sub_type if req.Provider == consts.ModelProviderSiliconFlow && req.Type == consts.ModelTypeCoder { q["sub_type"] = "chat" @@ -289,6 +304,7 @@ func (m *ModelUsecase) GetProviderModelList(ctx context.Context, req *domain.Get } u.Path = path.Join(u.Path, "/models") client := request.NewClient(u.Scheme, u.Host, m.client.Timeout, request.WithClient(m.client)) + client.SetDebug(m.cfg.Debug) query := m.getQuery(req) resp, err := request.Get[domain.OpenAIResp]( client, u.Path, diff --git a/backend/internal/openai/usecase/openai.go b/backend/internal/openai/usecase/openai.go index a98b357..5a4abe4 100644 --- a/backend/internal/openai/usecase/openai.go +++ b/backend/internal/openai/usecase/openai.go @@ -3,9 +3,10 @@ package openai import ( "bytes" "context" + "html/template" "log/slog" - "text/template" + "github.com/chaitin/MonkeyCode/backend/ent/types" "github.com/chaitin/MonkeyCode/backend/pkg/cvt" "github.com/chaitin/MonkeyCode/backend/config" @@ -15,16 +16,23 @@ import ( ) type OpenAIUsecase struct { - repo domain.OpenAIRepo - cfg *config.Config - logger *slog.Logger + repo domain.OpenAIRepo + modelRepo domain.ModelRepo + cfg *config.Config + logger *slog.Logger } -func NewOpenAIUsecase(cfg *config.Config, repo domain.OpenAIRepo, logger *slog.Logger) domain.OpenAIUsecase { +func NewOpenAIUsecase( + cfg *config.Config, + repo domain.OpenAIRepo, + modelRepo domain.ModelRepo, + logger *slog.Logger, +) domain.OpenAIUsecase { return &OpenAIUsecase{ - repo: repo, - cfg: cfg, - logger: logger, + repo: repo, + modelRepo: modelRepo, + cfg: cfg, + logger: logger, } } @@ -53,21 +61,17 @@ func (u *OpenAIUsecase) GetConfig(ctx context.Context, req *domain.ConfigReq) (* if err != nil { return nil, err } - - model, err := u.repo.ModelList(ctx) + llm, err := u.modelRepo.GetWithCache(ctx, consts.ModelTypeLLM) + if err != nil { + return nil, err + } + coder, err := u.modelRepo.GetWithCache(ctx, consts.ModelTypeCoder) if err != nil { return nil, err } - chatModel := "" - codeModel := "" - for _, m := range model { - switch m.ModelType { - case consts.ModelTypeLLM: - chatModel = m.ModelName - case consts.ModelTypeCoder: - codeModel = m.ModelName - } + if llm.Parameters == nil { + llm.Parameters = types.DefaultModelParam() } t, err := template.New("config").Parse(string(config.ConfigTmpl)) @@ -75,13 +79,21 @@ func (u *OpenAIUsecase) GetConfig(ctx context.Context, req *domain.ConfigReq) (* return nil, err } + u.logger.With("param", llm.Parameters).DebugContext(ctx, "get config") cnt := bytes.NewBuffer(nil) - if err := t.Execute(cnt, map[string]string{ - "apiBase": req.BaseURL, - "apikey": apiKey.Key, - "chatModel": chatModel, - "codeModel": codeModel, - }); err != nil { + data := map[string]any{ + "apiBase": req.BaseURL, + "apikey": apiKey.Key, + "chatModel": llm.ModelName, + "codeModel": coder.ModelName, + "r1Enabled": llm.Parameters.R1Enabled, + "maxTokens": llm.Parameters.MaxTokens, + "contextWindow": llm.Parameters.ContextWindow, + "supportsImages": llm.Parameters.SupprtImages, + "supportsComputerUse": llm.Parameters.SupportComputerUse, + "supportsPromptCache": llm.Parameters.SupportPromptCache, + } + if err := t.Execute(cnt, data); err != nil { return nil, err } diff --git a/ui/src/api/Cli.ts b/ui/src/api/Cli.ts new file mode 100644 index 0000000..6b8565a --- /dev/null +++ b/ui/src/api/Cli.ts @@ -0,0 +1,40 @@ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +/* + * --------------------------------------------------------------- + * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## + * ## ## + * ## AUTHOR: acacode ## + * ## SOURCE: https://github.com/acacode/swagger-typescript-api ## + * --------------------------------------------------------------- + */ + +import request, { ContentType, RequestParams } from "./httpClient"; +import { DomainCodeFiles, DomainIndexResult, V1CliCreateParams } from "./types"; + +/** + * @description 运行monkeycode-cli命令 + * + * @tags CLI + * @name V1CliCreate + * @summary 运行monkeycode-cli命令 + * @request POST:/api/v1/cli/{command} + * @response `200` `(DomainIndexResult)[]` 输出结果 + * @response `500` `WebResp` 内部错误 + */ + +export const v1CliCreate = ( + { command, ...query }: V1CliCreateParams, + codeFiles: DomainCodeFiles, + params: RequestParams = {}, +) => + request({ + path: `/api/v1/cli/${command}`, + method: "POST", + query: query, + body: codeFiles, + type: ContentType.Json, + format: "json", + ...params, + }); diff --git a/ui/src/api/WorkspaceFile.ts b/ui/src/api/WorkspaceFile.ts new file mode 100644 index 0000000..aae0a25 --- /dev/null +++ b/ui/src/api/WorkspaceFile.ts @@ -0,0 +1,312 @@ +/* eslint-disable */ +/* tslint:disable */ +// @ts-nocheck +/* + * --------------------------------------------------------------- + * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## + * ## ## + * ## AUTHOR: acacode ## + * ## SOURCE: https://github.com/acacode/swagger-typescript-api ## + * --------------------------------------------------------------- + */ + +import request, { ContentType, RequestParams } from "./httpClient"; +import { + DomainBatchCreateWorkspaceFileReq, + DomainBatchUpdateWorkspaceFileReq, + DomainCreateWorkspaceFileReq, + DomainGetAndSaveReq, + DomainListWorkspaceFileResp, + DomainSyncWorkspaceFileReq, + DomainSyncWorkspaceFileResp, + DomainUpdateWorkspaceFileReq, + DomainWorkspaceFile, + GetGetWorkspaceFileByPathParams, + GetListWorkspaceFilesParams, + WebResp, +} from "./types"; + +/** + * @description 分页获取工作区文件列表 + * + * @tags WorkspaceFile + * @name GetListWorkspaceFiles + * @summary 获取工作区文件列表 + * @request GET:/api/v1/workspace/files + * @response `200` `(WebResp & { + data?: DomainListWorkspaceFileResp, + +})` OK + */ + +export const getListWorkspaceFiles = ( + query: GetListWorkspaceFilesParams, + params: RequestParams = {}, +) => + request< + WebResp & { + data?: DomainListWorkspaceFileResp; + } + >({ + path: `/api/v1/workspace/files`, + method: "GET", + query: query, + type: ContentType.Json, + format: "json", + ...params, + }); + +/** + * @description 创建一个新的工作区文件 + * + * @tags WorkspaceFile + * @name PostCreateWorkspaceFile + * @summary 创建工作区文件 + * @request POST:/api/v1/workspace/files + * @response `200` `(WebResp & { + data?: DomainWorkspaceFile, + +})` OK + */ + +export const postCreateWorkspaceFile = ( + file: DomainCreateWorkspaceFileReq, + params: RequestParams = {}, +) => + request< + WebResp & { + data?: DomainWorkspaceFile; + } + >({ + path: `/api/v1/workspace/files`, + method: "POST", + body: file, + type: ContentType.Json, + format: "json", + ...params, + }); + +/** + * @description 批量更新多个工作区文件 + * + * @tags WorkspaceFile + * @name PutBatchUpdateWorkspaceFiles + * @summary 批量更新工作区文件 + * @request PUT:/api/v1/workspace/files/batch + * @response `200` `(WebResp & { + data?: (DomainWorkspaceFile)[], + +})` OK + */ + +export const putBatchUpdateWorkspaceFiles = ( + files: DomainBatchUpdateWorkspaceFileReq, + params: RequestParams = {}, +) => + request< + WebResp & { + data?: DomainWorkspaceFile[]; + } + >({ + path: `/api/v1/workspace/files/batch`, + method: "PUT", + body: files, + type: ContentType.Json, + format: "json", + ...params, + }); + +/** + * @description 批量创建多个工作区文件 + * + * @tags WorkspaceFile + * @name PostBatchCreateWorkspaceFiles + * @summary 批量创建工作区文件 + * @request POST:/api/v1/workspace/files/batch + * @response `200` `(WebResp & { + data?: (DomainWorkspaceFile)[], + +})` OK + */ + +export const postBatchCreateWorkspaceFiles = ( + files: DomainBatchCreateWorkspaceFileReq, + params: RequestParams = {}, +) => + request< + WebResp & { + data?: DomainWorkspaceFile[]; + } + >({ + path: `/api/v1/workspace/files/batch`, + method: "POST", + body: files, + type: ContentType.Json, + format: "json", + ...params, + }); + +/** + * @description 根据用户ID、项目ID和文件路径获取工作区文件 + * + * @tags WorkspaceFile + * @name GetGetWorkspaceFileByPath + * @summary 根据路径获取工作区文件 + * @request GET:/api/v1/workspace/files/by-path + * @response `200` `(WebResp & { + data?: DomainWorkspaceFile, + +})` OK + */ + +export const getGetWorkspaceFileByPath = ( + query: GetGetWorkspaceFileByPathParams, + params: RequestParams = {}, +) => + request< + WebResp & { + data?: DomainWorkspaceFile; + } + >({ + path: `/api/v1/workspace/files/by-path`, + method: "GET", + query: query, + type: ContentType.Json, + format: "json", + ...params, + }); + +/** + * No description + * + * @tags WorkspaceFile + * @name V1WorkspaceFilesGetAndSaveCreate + * @summary 获取并保存工作区文件 + * @request POST:/api/v1/workspace/files/get-and-save + * @response `200` `WebResp` OK + */ + +export const v1WorkspaceFilesGetAndSaveCreate = ( + req: DomainGetAndSaveReq, + params: RequestParams = {}, +) => + request({ + path: `/api/v1/workspace/files/get-and-save`, + method: "POST", + body: req, + type: ContentType.Json, + format: "json", + ...params, + }); + +/** + * @description 同步本地文件到工作区,智能检测新增、修改和删除 + * + * @tags WorkspaceFile + * @name PostSyncWorkspaceFiles + * @summary 同步工作区文件 + * @request POST:/api/v1/workspace/files/sync + * @response `200` `(WebResp & { + data?: DomainSyncWorkspaceFileResp, + +})` OK + */ + +export const postSyncWorkspaceFiles = ( + sync: DomainSyncWorkspaceFileReq, + params: RequestParams = {}, +) => + request< + WebResp & { + data?: DomainSyncWorkspaceFileResp; + } + >({ + path: `/api/v1/workspace/files/sync`, + method: "POST", + body: sync, + type: ContentType.Json, + format: "json", + ...params, + }); + +/** + * @description 根据文件ID获取工作区文件详情 + * + * @tags WorkspaceFile + * @name GetGetWorkspaceFileById + * @summary 根据ID获取工作区文件 + * @request GET:/api/v1/workspace/files/{id} + * @response `200` `(WebResp & { + data?: DomainWorkspaceFile, + +})` OK + */ + +export const getGetWorkspaceFileById = ( + id: string, + params: RequestParams = {}, +) => + request< + WebResp & { + data?: DomainWorkspaceFile; + } + >({ + path: `/api/v1/workspace/files/${id}`, + method: "GET", + type: ContentType.Json, + format: "json", + ...params, + }); + +/** + * @description 更新指定的工作区文件 + * + * @tags WorkspaceFile + * @name PutUpdateWorkspaceFile + * @summary 更新工作区文件 + * @request PUT:/api/v1/workspace/files/{id} + * @response `200` `(WebResp & { + data?: DomainWorkspaceFile, + +})` OK + */ + +export const putUpdateWorkspaceFile = ( + id: string, + file: DomainUpdateWorkspaceFileReq, + params: RequestParams = {}, +) => + request< + WebResp & { + data?: DomainWorkspaceFile; + } + >({ + path: `/api/v1/workspace/files/${id}`, + method: "PUT", + body: file, + type: ContentType.Json, + format: "json", + ...params, + }); + +/** + * @description 删除指定的工作区文件 + * + * @tags WorkspaceFile + * @name DeleteDeleteWorkspaceFile + * @summary 删除工作区文件 + * @request DELETE:/api/v1/workspace/files/{id} + * @response `200` `WebResp` OK + */ + +export const deleteDeleteWorkspaceFile = ( + id: string, + params: RequestParams = {}, +) => + request({ + path: `/api/v1/workspace/files/${id}`, + method: "DELETE", + type: ContentType.Json, + format: "json", + ...params, + }); diff --git a/ui/src/api/index.ts b/ui/src/api/index.ts index 424477f..c9c02ae 100644 --- a/ui/src/api/index.ts +++ b/ui/src/api/index.ts @@ -1,5 +1,6 @@ export * from './Admin' export * from './Billing' +export * from './Cli' export * from './Dashboard' export * from './Model' export * from './OpenAiv1' @@ -7,5 +8,6 @@ export * from './User' export * from './UserDashboard' export * from './UserManage' export * from './UserRecord' +export * from './WorkspaceFile' export * from './types' diff --git a/ui/src/api/types.ts b/ui/src/api/types.ts index 66ef746..189846a 100644 --- a/ui/src/api/types.ts +++ b/ui/src/api/types.ts @@ -10,6 +10,24 @@ * --------------------------------------------------------------- */ +export enum DomainCodeLanguageType { + CodeLanguageTypeGo = "go", + CodeLanguageTypePython = "python", + CodeLanguageTypeJava = "java", + CodeLanguageTypeJavaScript = "javascript", + CodeLanguageTypeTypeScript = "typescript", + CodeLanguageTypeJSX = "jsx", + CodeLanguageTypeTSX = "tsx", + CodeLanguageTypeHTML = "html", + CodeLanguageTypeCSS = "css", + CodeLanguageTypePHP = "php", + CodeLanguageTypeRust = "rust", + CodeLanguageTypeSwift = "swift", + CodeLanguageTypeKotlin = "kotlin", + CodeLanguageTypeC = "c", + CodeLanguageTypeCpp = "cpp", +} + export enum ConstsUserStatus { UserStatusActive = "active", UserStatusInactive = "inactive", @@ -27,6 +45,9 @@ export enum ConstsReportAction { ReportActionSuggest = "suggest", ReportActionFileWritten = "file_written", ReportActionReject = "reject", + ReportActionNewTask = "new_task", + ReportActionFeedbackTask = "feedback_task", + ReportActionAbortTask = "abort_task", } export enum ConstsModelType { @@ -109,6 +130,20 @@ export interface DomainAllModelResp { providers?: DomainProviderModel[]; } +export interface DomainBatchCreateWorkspaceFileReq { + /** 文件列表 */ + files: DomainCreateWorkspaceFileReq[]; + /** 用户ID */ + user_id: string; + /** 工作区ID */ + workspace_id: string; +} + +export interface DomainBatchUpdateWorkspaceFileReq { + /** 文件列表 */ + files: DomainUpdateWorkspaceFileReq[]; +} + export interface DomainCategoryPoint { /** 分类 */ category?: string; @@ -127,7 +162,7 @@ export interface DomainChatContent { /** 内容 */ content?: string; created_at?: number; - /** 角色,如user: 用户的提问 assistant: 机器人回复 */ + /** 角色,如user: 用户的提问 assistant: 机器人回复 system: 系统消息 */ role?: ConstsChatRole; } @@ -170,6 +205,10 @@ export interface DomainCheckModelReq { type: "llm" | "coder" | "embedding" | "rerank"; } +export interface DomainCodeFiles { + files?: DomainFileMeta[]; +} + export interface DomainCompletionData { /** 代码行数 */ code_lines?: number; @@ -255,6 +294,8 @@ export interface DomainCreateModelReq { model_name: string; /** 模型类型 llm:对话模型 coder:代码模型 */ model_type?: ConstsModelType; + /** 高级参数 */ + param?: DomainModelParam; /** 提供商 */ provider: | "SiliconFlow" @@ -272,6 +313,23 @@ export interface DomainCreateModelReq { show_name?: string; } +export interface DomainCreateWorkspaceFileReq { + /** 文件内容 */ + content?: string; + /** 文件哈希 */ + hash: string; + /** 编程语言 */ + language?: string; + /** 文件路径 */ + path: string; + /** 文件大小 */ + size?: number; + /** 用户ID */ + user_id: string; + /** 工作区ID */ + workspace_id: string; +} + export interface DomainCustomOAuth { /** 自定义OAuth访问令牌URL */ access_token_url?: string; @@ -347,6 +405,26 @@ export interface DomainExportCompletionDataResp { total_count?: number; } +export interface DomainFileMeta { + /** 文件内容(可选) */ + content?: string; + fileExtension?: string; + /** 文件哈希(可选) */ + fileHash?: string; + filePath?: string; + /** 语言类型(可选) */ + language?: DomainCodeLanguageType; +} + +export interface DomainGetAndSaveReq { + /** 代码文件信息 */ + code_files: DomainFileMeta[]; + /** 用户ID */ + user_id: string; + /** 项目ID */ + workspace_id: string; +} + export interface DomainGetProviderModelListResp { models?: DomainProviderModelListItem[]; } @@ -366,6 +444,26 @@ export interface DomainIPInfo { province?: string; } +export interface DomainIndexResult { + definition?: { + name?: string; + returnType?: string; + type?: string; + }; + definitionText?: string; + endLine?: number; + fileHash?: string; + filePath?: string; + implementText?: string; + language?: string; + name?: string; + rangeText?: string; + scope?: Record[]; + signature?: string; + startLine?: number; + type?: string; +} + export interface DomainInviteResp { /** 邀请码 */ code?: string; @@ -413,6 +511,13 @@ export interface DomainListUserResp { users?: DomainUser[]; } +export interface DomainListWorkspaceFileResp { + files?: DomainWorkspaceFile[]; + has_next_page?: boolean; + next_token?: string; + total_count?: number; +} + export interface DomainLoginReq { /** 密码 */ password?: string; @@ -459,6 +564,8 @@ export interface DomainModel { model_type?: ConstsModelType; /** 输出token数 */ output?: number; + /** 高级参数 */ + param?: DomainModelParam; /** 提供商 */ provider?: ConstsModelProvider; /** 模型显示名称 */ @@ -506,6 +613,15 @@ export interface DomainModelListResp { object?: string; } +export interface DomainModelParam { + context_window?: number; + max_tokens?: number; + r1_enabled?: boolean; + support_computer_use?: boolean; + support_images?: boolean; + support_prompt_cache?: boolean; +} + export interface DomainModelTokenUsage { /** 时间戳 */ timestamp?: number; @@ -569,6 +685,8 @@ export interface DomainReportReq { cursor_position?: Record; /** task_id or resp_id */ id?: string; + /** 模式 */ + mode?: string; /** 当前文件的原文(用于reject action) */ source_code?: string; /** 工具 */ @@ -603,6 +721,26 @@ export interface DomainStatistics { total_users?: number; } +export interface DomainSyncWorkspaceFileReq { + /** 要同步的文件列表 */ + files: DomainCreateWorkspaceFileReq[]; + /** 用户ID */ + user_id: string; + /** 工作区ID */ + workspace_id: string; +} + +export interface DomainSyncWorkspaceFileResp { + /** 新创建的文件 */ + created?: DomainWorkspaceFile[]; + /** 删除的文件ID */ + deleted?: string[]; + /** 处理的文件总数 */ + total?: number; + /** 更新的文件 */ + updated?: DomainWorkspaceFile[]; +} + export interface DomainTimeStat { /** 接受率统计 */ accepted_per?: { @@ -669,6 +807,8 @@ export interface DomainUpdateModelReq { id?: string; /** 模型名称 */ model_name?: string; + /** 高级参数 */ + param?: DomainModelParam; /** 提供商 */ provider: | "SiliconFlow" @@ -712,6 +852,19 @@ export interface DomainUpdateUserReq { status?: ConstsUserStatus; } +export interface DomainUpdateWorkspaceFileReq { + /** 文件内容 */ + content?: string; + /** 文件哈希 */ + hash?: string; + /** 文件ID */ + id: string; + /** 编程语言 */ + language?: string; + /** 文件大小 */ + size?: number; +} + export interface DomainUser { /** 头像URL */ avatar_url?: string; @@ -819,6 +972,29 @@ export interface DomainUserStat { work_mode?: DomainCategoryPoint[]; } +export interface DomainWorkspaceFile { + /** 文件内容 */ + content?: string; + /** 创建时间 */ + created_at?: number; + /** 文件哈希 */ + hash?: string; + /** 文件ID */ + id?: string; + /** 编程语言 */ + language?: string; + /** 文件路径 */ + path?: string; + /** 文件大小 */ + size?: number; + /** 更新时间 */ + updated_at?: number; + /** 用户ID */ + user_id?: string; + /** 工作区ID */ + workspace_id?: string; +} + export interface WebResp { code?: number; data?: unknown; @@ -833,18 +1009,34 @@ export interface DeleteDeleteAdminParams { export interface GetListAdminUserParams { /** 下一页标识 */ next_token?: string; - /** 分页 */ + /** + * 分页 + * @min 1 + * @default 1 + */ page?: number; - /** 每页多少条记录 */ + /** + * 每页多少条记录 + * @min 1 + * @default 10 + */ size?: number; } export interface GetAdminLoginHistoryParams { /** 下一页标识 */ next_token?: string; - /** 分页 */ + /** + * 分页 + * @min 1 + * @default 1 + */ page?: number; - /** 每页多少条记录 */ + /** + * 每页多少条记录 + * @min 1 + * @default 10 + */ size?: number; } @@ -862,9 +1054,17 @@ export interface GetListChatRecordParams { language?: string; /** 下一页标识 */ next_token?: string; - /** 分页 */ + /** + * 分页 + * @min 1 + * @default 1 + */ page?: number; - /** 每页多少条记录 */ + /** + * 每页多少条记录 + * @min 1 + * @default 10 + */ size?: number; /** 工作模式 */ work_mode?: string; @@ -884,14 +1084,29 @@ export interface GetListCompletionRecordParams { language?: string; /** 下一页标识 */ next_token?: string; - /** 分页 */ + /** + * 分页 + * @min 1 + * @default 1 + */ page?: number; - /** 每页多少条记录 */ + /** + * 每页多少条记录 + * @min 1 + * @default 10 + */ size?: number; /** 工作模式 */ work_mode?: string; } +export interface V1CliCreateParams { + /** 标志 */ + flag?: string; + /** 命令 */ + command: string; +} + export interface GetCategoryStatDashboardParams { /** * 持续时间 (小时或天数)` @@ -1029,9 +1244,17 @@ export interface GetUserListChatRecordParams { language?: string; /** 下一页标识 */ next_token?: string; - /** 分页 */ + /** + * 分页 + * @min 1 + * @default 1 + */ page?: number; - /** 每页多少条记录 */ + /** + * 每页多少条记录 + * @min 1 + * @default 10 + */ size?: number; /** 工作模式 */ work_mode?: string; @@ -1051,9 +1274,17 @@ export interface GetUserListCompletionRecordParams { language?: string; /** 下一页标识 */ next_token?: string; - /** 分页 */ + /** + * 分页 + * @min 1 + * @default 1 + */ page?: number; - /** 每页多少条记录 */ + /** + * 每页多少条记录 + * @min 1 + * @default 10 + */ size?: number; /** 工作模式 */ work_mode?: string; @@ -1101,18 +1332,34 @@ export interface DeleteDeleteUserParams { export interface GetListUserParams { /** 下一页标识 */ next_token?: string; - /** 分页 */ + /** + * 分页 + * @min 1 + * @default 1 + */ page?: number; - /** 每页多少条记录 */ + /** + * 每页多少条记录 + * @min 1 + * @default 10 + */ size?: number; } export interface GetLoginHistoryParams { /** 下一页标识 */ next_token?: string; - /** 分页 */ + /** + * 分页 + * @min 1 + * @default 1 + */ page?: number; - /** 每页多少条记录 */ + /** + * 每页多少条记录 + * @min 1 + * @default 10 + */ size?: number; } @@ -1136,3 +1383,37 @@ export interface GetUserOauthSignupOrInParams { */ source: "plugin" | "browser"; } + +export interface GetListWorkspaceFilesParams { + /** 编程语言筛选 */ + language?: string; + /** 下一页标识 */ + next_token?: string; + /** + * 分页 + * @min 1 + * @default 1 + */ + page?: number; + /** 搜索关键词(文件路径) */ + search?: string; + /** + * 每页多少条记录 + * @min 1 + * @default 10 + */ + size?: number; + /** 用户ID */ + user_id?: string; + /** 工作区ID */ + workspace_id?: string; +} + +export interface GetGetWorkspaceFileByPathParams { + /** 用户ID */ + user_id?: string; + /** 项目ID */ + project_id: string; + /** 文件路径 */ + path: string; +} diff --git a/ui/src/pages/model/components/modelModal.tsx b/ui/src/pages/model/components/modelModal.tsx index 16077d3..ddd0ed5 100644 --- a/ui/src/pages/model/components/modelModal.tsx +++ b/ui/src/pages/model/components/modelModal.tsx @@ -15,10 +15,16 @@ import { TextField, useTheme, alpha as addOpacityToColor, + Accordion, + AccordionSummary, + AccordionDetails, + Checkbox, + FormControlLabel, } from '@mui/material'; import { Icon, message, Modal } from '@c-x/ui'; import { useEffect, useState } from 'react'; import { Controller, useForm } from 'react-hook-form'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; interface AddModelProps { open: boolean; data: DomainModel | null; @@ -37,6 +43,13 @@ interface AddModelForm { api_header_value: string; type: ConstsModelType; show_name: string; + // 高级设置字段 + context_window_size: number; + max_output_tokens: number; + enable_r1_params: boolean; + support_image: boolean; + support_compute: boolean; + support_prompt_caching: boolean; } const titleMap = { @@ -76,6 +89,13 @@ const ModelAdd = ({ api_header_key: '', api_header_value: '', show_name: '', + // 高级设置默认值 + context_window_size: 64000, + max_output_tokens: 8192, + enable_r1_params: false, + support_image: false, + support_compute: false, + support_prompt_caching: false, }, }); @@ -87,6 +107,7 @@ const ModelAdd = ({ const [modelLoading, setModelLoading] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(false); + const [expandAdvanced, setExpandAdvanced] = useState(false); const handleReset = () => { onClose(); @@ -99,12 +120,21 @@ const ModelAdd = ({ api_version: '', api_header_key: '', api_header_value: '', + // 重置高级设置 + context_window_size: 64000, + max_output_tokens: 8192, + enable_r1_params: false, + support_image: false, + support_compute: false, + support_prompt_caching: false, }); setModelUserList([]); setSuccess(false); setLoading(false); setModelLoading(false); setError(''); + // 重置高级设置的展开状态 + setExpandAdvanced(false); refresh(); }; @@ -171,6 +201,15 @@ const ModelAdd = ({ id: data.id, provider: value.provider as Exclude, show_name: value.show_name, + // 添加高级设置字段到 param 对象中 + param: { + context_window: value.context_window_size, + max_tokens: value.max_output_tokens, + r1_enabled: value.enable_r1_params, + support_images: value.support_image, + support_computer_use: value.support_compute, + support_prompt_cache: value.support_prompt_caching, + }, }) .then(() => { message.success('修改成功'); @@ -188,6 +227,15 @@ const ModelAdd = ({ api_header: header, provider: value.provider as Exclude, show_name: value.show_name, + // 添加高级设置字段到 param 对象中 + param: { + context_window: value.context_window_size, + max_tokens: value.max_output_tokens, + r1_enabled: value.enable_r1_params, + support_images: value.support_image, + support_computer_use: value.support_compute, + support_prompt_cache: value.support_prompt_caching, + }, }) .then(() => { message.success('添加成功'); @@ -216,6 +264,12 @@ const ModelAdd = ({ api_header_value: value.api_header?.split('=')[1] || '', type, show_name: value.show_name || '', + context_window_size: 64000, + max_output_tokens: 8192, + enable_r1_params: false, + support_image: false, + support_compute: false, + support_prompt_caching: false }); } reset({ @@ -228,6 +282,12 @@ const ModelAdd = ({ api_header_key: value.api_header?.split('=')[0] || '', api_header_value: value.api_header?.split('=')[1] || '', show_name: value.show_name || '', + context_window_size: value.param?.context_window || 64000, + max_output_tokens: value.param?.max_tokens || 8192, + enable_r1_params: value.param?.r1_enabled || false, + support_image: value.param?.support_images || false, + support_compute: value.param?.support_computer_use || false, + support_prompt_caching: value.param?.support_prompt_cache || false }); }; @@ -247,8 +307,17 @@ const ModelAdd = ({ api_header_key: '', api_header_value: '', show_name: '', + // 高级设置默认值 + context_window_size: 64000, + max_output_tokens: 8192, + enable_r1_params: false, + support_image: false, + support_compute: false, + support_prompt_caching: false, }); } + // 确保每次打开时高级设置都是折叠的 + setExpandAdvanced(false); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [data, open]); @@ -322,6 +391,13 @@ const ModelAdd = ({ api_header_key: '', api_header_value: '', show_name: '', + // 重置高级设置 + context_window_size: 64000, + max_output_tokens: 8192, + enable_r1_params: false, + support_image: false, + support_compute: false, + support_prompt_caching: false, }); } }} @@ -589,6 +665,219 @@ const ModelAdd = ({ )} + + {/* 高级设置部分 */} + + setExpandAdvanced(!expandAdvanced)} + > + + 高级设置 + + + + + + 上下文窗口大小 + + ( + <> + + + {[ + { label: '128k', value: 128000 }, + { label: '256k', value: 256000 }, + { label: '512k', value: 512000 }, + { label: '1m', value: 1_000_000 } + ].map((option) => ( + field.onChange(option.value)} + > + {option.label} + + ))} + + + )} + /> + + + + + 最大输出 Token + + ( + + )} + /> + + + {/* 复选框组 - 使用更紧凑的布局 */} + + ( + field.onChange(e.target.checked)} + size='small' + /> + } + label={ + + 启用 R1 模型参数 + + (使用 QWQ 等 R1 系列模型时必须启用,避免出现 400 错误) + + + } + sx={{ margin: 0 }} + /> + )} + /> + + ( + field.onChange(e.target.checked)} + size='small' + /> + } + label={ + + 图像支持 + + (此模型是否能够处理和理解图像?) + + + } + sx={{ margin: 0 }} + /> + )} + /> + + ( + field.onChange(e.target.checked)} + size='small' + /> + } + label={ + + 计算机功能调用 + + (此模型是否能够与浏览器交互?) + + + } + sx={{ margin: 0 }} + /> + )} + /> + + ( + field.onChange(e.target.checked)} + size='small' + /> + } + label={ + + 提示缓存 + + (此模型是否能够缓存提示?) + + + } + sx={{ margin: 0 }} + /> + )} + /> + + + + + )} {error && (