diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go index 0703bf7..2bd3e03 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 github.com/google/wire/cmd/wire +//go:generate go run -mod=mod github.com/google/wire/cmd/wire //go:build !wireinject // +build !wireinject diff --git a/backend/db/migrate/schema.go b/backend/db/migrate/schema.go index b028396..b9e953d 100644 --- a/backend/db/migrate/schema.go +++ b/backend/db/migrate/schema.go @@ -266,13 +266,14 @@ var ( {Name: "is_accept", Type: field.TypeBool, Default: false}, {Name: "program_language", Type: field.TypeString, Nullable: true}, {Name: "work_mode", Type: field.TypeString, Nullable: true}, + {Name: "prompt", Type: field.TypeString, Nullable: true}, {Name: "completion", Type: field.TypeString, Nullable: true}, {Name: "code_lines", Type: field.TypeInt64, Nullable: true}, {Name: "input_tokens", Type: field.TypeInt64, Nullable: true}, {Name: "output_tokens", Type: field.TypeInt64, Nullable: true}, {Name: "is_suggested", Type: field.TypeBool, Default: false}, {Name: "source_code", Type: field.TypeString, Nullable: true}, - {Name: "cursor_position", Type: field.TypeInt64, Nullable: true}, + {Name: "cursor_position", Type: field.TypeJSON, Nullable: true}, {Name: "user_input", Type: field.TypeString, Nullable: true}, {Name: "created_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime}, @@ -287,13 +288,13 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "tasks_models_tasks", - Columns: []*schema.Column{TasksColumns[17]}, + Columns: []*schema.Column{TasksColumns[18]}, RefColumns: []*schema.Column{ModelsColumns[0]}, OnDelete: schema.SetNull, }, { Symbol: "tasks_users_tasks", - Columns: []*schema.Column{TasksColumns[18]}, + Columns: []*schema.Column{TasksColumns[19]}, RefColumns: []*schema.Column{UsersColumns[0]}, OnDelete: schema.SetNull, }, diff --git a/backend/db/mutation.go b/backend/db/mutation.go index 4963155..c451827 100644 --- a/backend/db/mutation.go +++ b/backend/db/mutation.go @@ -9869,6 +9869,7 @@ type TaskMutation struct { is_accept *bool program_language *string work_mode *string + prompt *string completion *string code_lines *int64 addcode_lines *int64 @@ -9878,8 +9879,7 @@ type TaskMutation struct { addoutput_tokens *int64 is_suggested *bool source_code *string - cursor_position *int64 - addcursor_position *int64 + cursor_position *map[string]interface{} user_input *string created_at *time.Time updated_at *time.Time @@ -10353,6 +10353,55 @@ func (m *TaskMutation) ResetWorkMode() { delete(m.clearedFields, task.FieldWorkMode) } +// SetPrompt sets the "prompt" field. +func (m *TaskMutation) SetPrompt(s string) { + m.prompt = &s +} + +// Prompt returns the value of the "prompt" field in the mutation. +func (m *TaskMutation) Prompt() (r string, exists bool) { + v := m.prompt + if v == nil { + return + } + return *v, true +} + +// OldPrompt returns the old "prompt" field's value of the Task entity. +// If the Task 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 *TaskMutation) OldPrompt(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPrompt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPrompt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPrompt: %w", err) + } + return oldValue.Prompt, nil +} + +// ClearPrompt clears the value of the "prompt" field. +func (m *TaskMutation) ClearPrompt() { + m.prompt = nil + m.clearedFields[task.FieldPrompt] = struct{}{} +} + +// PromptCleared returns if the "prompt" field was cleared in this mutation. +func (m *TaskMutation) PromptCleared() bool { + _, ok := m.clearedFields[task.FieldPrompt] + return ok +} + +// ResetPrompt resets all changes to the "prompt" field. +func (m *TaskMutation) ResetPrompt() { + m.prompt = nil + delete(m.clearedFields, task.FieldPrompt) +} + // SetCompletion sets the "completion" field. func (m *TaskMutation) SetCompletion(s string) { m.completion = &s @@ -10698,13 +10747,12 @@ func (m *TaskMutation) ResetSourceCode() { } // SetCursorPosition sets the "cursor_position" field. -func (m *TaskMutation) SetCursorPosition(i int64) { - m.cursor_position = &i - m.addcursor_position = nil +func (m *TaskMutation) SetCursorPosition(value map[string]interface{}) { + m.cursor_position = &value } // CursorPosition returns the value of the "cursor_position" field in the mutation. -func (m *TaskMutation) CursorPosition() (r int64, exists bool) { +func (m *TaskMutation) CursorPosition() (r map[string]interface{}, exists bool) { v := m.cursor_position if v == nil { return @@ -10715,7 +10763,7 @@ func (m *TaskMutation) CursorPosition() (r int64, exists bool) { // OldCursorPosition returns the old "cursor_position" field's value of the Task entity. // If the Task 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 *TaskMutation) OldCursorPosition(ctx context.Context) (v int64, err error) { +func (m *TaskMutation) OldCursorPosition(ctx context.Context) (v map[string]interface{}, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCursorPosition is only allowed on UpdateOne operations") } @@ -10729,28 +10777,9 @@ func (m *TaskMutation) OldCursorPosition(ctx context.Context) (v int64, err erro return oldValue.CursorPosition, nil } -// AddCursorPosition adds i to the "cursor_position" field. -func (m *TaskMutation) AddCursorPosition(i int64) { - if m.addcursor_position != nil { - *m.addcursor_position += i - } else { - m.addcursor_position = &i - } -} - -// AddedCursorPosition returns the value that was added to the "cursor_position" field in this mutation. -func (m *TaskMutation) AddedCursorPosition() (r int64, exists bool) { - v := m.addcursor_position - if v == nil { - return - } - return *v, true -} - // ClearCursorPosition clears the value of the "cursor_position" field. func (m *TaskMutation) ClearCursorPosition() { m.cursor_position = nil - m.addcursor_position = nil m.clearedFields[task.FieldCursorPosition] = struct{}{} } @@ -10763,7 +10792,6 @@ func (m *TaskMutation) CursorPositionCleared() bool { // ResetCursorPosition resets all changes to the "cursor_position" field. func (m *TaskMutation) ResetCursorPosition() { m.cursor_position = nil - m.addcursor_position = nil delete(m.clearedFields, task.FieldCursorPosition) } @@ -11030,7 +11058,7 @@ func (m *TaskMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *TaskMutation) Fields() []string { - fields := make([]string, 0, 18) + fields := make([]string, 0, 19) if m.task_id != nil { fields = append(fields, task.FieldTaskID) } @@ -11055,6 +11083,9 @@ func (m *TaskMutation) Fields() []string { if m.work_mode != nil { fields = append(fields, task.FieldWorkMode) } + if m.prompt != nil { + fields = append(fields, task.FieldPrompt) + } if m.completion != nil { fields = append(fields, task.FieldCompletion) } @@ -11109,6 +11140,8 @@ func (m *TaskMutation) Field(name string) (ent.Value, bool) { return m.ProgramLanguage() case task.FieldWorkMode: return m.WorkMode() + case task.FieldPrompt: + return m.Prompt() case task.FieldCompletion: return m.Completion() case task.FieldCodeLines: @@ -11154,6 +11187,8 @@ func (m *TaskMutation) OldField(ctx context.Context, name string) (ent.Value, er return m.OldProgramLanguage(ctx) case task.FieldWorkMode: return m.OldWorkMode(ctx) + case task.FieldPrompt: + return m.OldPrompt(ctx) case task.FieldCompletion: return m.OldCompletion(ctx) case task.FieldCodeLines: @@ -11239,6 +11274,13 @@ func (m *TaskMutation) SetField(name string, value ent.Value) error { } m.SetWorkMode(v) return nil + case task.FieldPrompt: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPrompt(v) + return nil case task.FieldCompletion: v, ok := value.(string) if !ok { @@ -11282,7 +11324,7 @@ func (m *TaskMutation) SetField(name string, value ent.Value) error { m.SetSourceCode(v) return nil case task.FieldCursorPosition: - v, ok := value.(int64) + v, ok := value.(map[string]interface{}) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -11326,9 +11368,6 @@ func (m *TaskMutation) AddedFields() []string { if m.addoutput_tokens != nil { fields = append(fields, task.FieldOutputTokens) } - if m.addcursor_position != nil { - fields = append(fields, task.FieldCursorPosition) - } return fields } @@ -11343,8 +11382,6 @@ func (m *TaskMutation) AddedField(name string) (ent.Value, bool) { return m.AddedInputTokens() case task.FieldOutputTokens: return m.AddedOutputTokens() - case task.FieldCursorPosition: - return m.AddedCursorPosition() } return nil, false } @@ -11375,13 +11412,6 @@ func (m *TaskMutation) AddField(name string, value ent.Value) error { } m.AddOutputTokens(v) return nil - case task.FieldCursorPosition: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCursorPosition(v) - return nil } return fmt.Errorf("unknown Task numeric field %s", name) } @@ -11405,6 +11435,9 @@ func (m *TaskMutation) ClearedFields() []string { if m.FieldCleared(task.FieldWorkMode) { fields = append(fields, task.FieldWorkMode) } + if m.FieldCleared(task.FieldPrompt) { + fields = append(fields, task.FieldPrompt) + } if m.FieldCleared(task.FieldCompletion) { fields = append(fields, task.FieldCompletion) } @@ -11455,6 +11488,9 @@ func (m *TaskMutation) ClearField(name string) error { case task.FieldWorkMode: m.ClearWorkMode() return nil + case task.FieldPrompt: + m.ClearPrompt() + return nil case task.FieldCompletion: m.ClearCompletion() return nil @@ -11508,6 +11544,9 @@ func (m *TaskMutation) ResetField(name string) error { case task.FieldWorkMode: m.ResetWorkMode() return nil + case task.FieldPrompt: + m.ResetPrompt() + return nil case task.FieldCompletion: m.ResetCompletion() return nil diff --git a/backend/db/runtime/runtime.go b/backend/db/runtime/runtime.go index d64a57c..af72064 100644 --- a/backend/db/runtime/runtime.go +++ b/backend/db/runtime/runtime.go @@ -257,15 +257,15 @@ func init() { // task.DefaultIsAccept holds the default value on creation for the is_accept field. task.DefaultIsAccept = taskDescIsAccept.Default.(bool) // taskDescIsSuggested is the schema descriptor for is_suggested field. - taskDescIsSuggested := taskFields[13].Descriptor() + taskDescIsSuggested := taskFields[14].Descriptor() // task.DefaultIsSuggested holds the default value on creation for the is_suggested field. task.DefaultIsSuggested = taskDescIsSuggested.Default.(bool) // taskDescCreatedAt is the schema descriptor for created_at field. - taskDescCreatedAt := taskFields[17].Descriptor() + taskDescCreatedAt := taskFields[18].Descriptor() // task.DefaultCreatedAt holds the default value on creation for the created_at field. task.DefaultCreatedAt = taskDescCreatedAt.Default.(func() time.Time) // taskDescUpdatedAt is the schema descriptor for updated_at field. - taskDescUpdatedAt := taskFields[18].Descriptor() + taskDescUpdatedAt := taskFields[19].Descriptor() // task.DefaultUpdatedAt holds the default value on creation for the updated_at field. task.DefaultUpdatedAt = taskDescUpdatedAt.Default.(func() time.Time) // task.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. diff --git a/backend/db/task.go b/backend/db/task.go index 2882f3c..e641915 100644 --- a/backend/db/task.go +++ b/backend/db/task.go @@ -3,6 +3,7 @@ package db import ( + "encoding/json" "fmt" "strings" "time" @@ -37,6 +38,8 @@ type Task struct { ProgramLanguage string `json:"program_language,omitempty"` // WorkMode holds the value of the "work_mode" field. WorkMode string `json:"work_mode,omitempty"` + // Prompt holds the value of the "prompt" field. + Prompt string `json:"prompt,omitempty"` // Completion holds the value of the "completion" field. Completion string `json:"completion,omitempty"` // CodeLines holds the value of the "code_lines" field. @@ -50,7 +53,7 @@ type Task struct { // SourceCode holds the value of the "source_code" field. SourceCode string `json:"source_code,omitempty"` // CursorPosition holds the value of the "cursor_position" field. - CursorPosition int64 `json:"cursor_position,omitempty"` + CursorPosition map[string]interface{} `json:"cursor_position,omitempty"` // UserInput holds the value of the "user_input" field. UserInput string `json:"user_input,omitempty"` // CreatedAt holds the value of the "created_at" field. @@ -112,11 +115,13 @@ func (*Task) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { + case task.FieldCursorPosition: + values[i] = new([]byte) case task.FieldIsAccept, task.FieldIsSuggested: values[i] = new(sql.NullBool) - case task.FieldCodeLines, task.FieldInputTokens, task.FieldOutputTokens, task.FieldCursorPosition: + case task.FieldCodeLines, task.FieldInputTokens, task.FieldOutputTokens: values[i] = new(sql.NullInt64) - case task.FieldTaskID, task.FieldRequestID, task.FieldModelType, task.FieldProgramLanguage, task.FieldWorkMode, task.FieldCompletion, task.FieldSourceCode, task.FieldUserInput: + case task.FieldTaskID, task.FieldRequestID, task.FieldModelType, task.FieldProgramLanguage, task.FieldWorkMode, task.FieldPrompt, task.FieldCompletion, task.FieldSourceCode, task.FieldUserInput: values[i] = new(sql.NullString) case task.FieldCreatedAt, task.FieldUpdatedAt: values[i] = new(sql.NullTime) @@ -191,6 +196,12 @@ func (t *Task) assignValues(columns []string, values []any) error { } else if value.Valid { t.WorkMode = value.String } + case task.FieldPrompt: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field prompt", values[i]) + } else if value.Valid { + t.Prompt = value.String + } case task.FieldCompletion: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field completion", values[i]) @@ -228,10 +239,12 @@ func (t *Task) assignValues(columns []string, values []any) error { t.SourceCode = value.String } case task.FieldCursorPosition: - if value, ok := values[i].(*sql.NullInt64); !ok { + if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field cursor_position", values[i]) - } else if value.Valid { - t.CursorPosition = value.Int64 + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &t.CursorPosition); err != nil { + return fmt.Errorf("unmarshal field cursor_position: %w", err) + } } case task.FieldUserInput: if value, ok := values[i].(*sql.NullString); !ok { @@ -326,6 +339,9 @@ func (t *Task) String() string { builder.WriteString("work_mode=") builder.WriteString(t.WorkMode) builder.WriteString(", ") + builder.WriteString("prompt=") + builder.WriteString(t.Prompt) + builder.WriteString(", ") builder.WriteString("completion=") builder.WriteString(t.Completion) builder.WriteString(", ") diff --git a/backend/db/task/task.go b/backend/db/task/task.go index 9454824..ac699e9 100644 --- a/backend/db/task/task.go +++ b/backend/db/task/task.go @@ -30,6 +30,8 @@ const ( FieldProgramLanguage = "program_language" // FieldWorkMode holds the string denoting the work_mode field in the database. FieldWorkMode = "work_mode" + // FieldPrompt holds the string denoting the prompt field in the database. + FieldPrompt = "prompt" // FieldCompletion holds the string denoting the completion field in the database. FieldCompletion = "completion" // FieldCodeLines holds the string denoting the code_lines field in the database. @@ -92,6 +94,7 @@ var Columns = []string{ FieldIsAccept, FieldProgramLanguage, FieldWorkMode, + FieldPrompt, FieldCompletion, FieldCodeLines, FieldInputTokens, @@ -175,6 +178,11 @@ func ByWorkMode(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldWorkMode, opts...).ToFunc() } +// ByPrompt orders the results by the prompt field. +func ByPrompt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPrompt, opts...).ToFunc() +} + // ByCompletion orders the results by the completion field. func ByCompletion(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCompletion, opts...).ToFunc() @@ -205,11 +213,6 @@ func BySourceCode(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldSourceCode, opts...).ToFunc() } -// ByCursorPosition orders the results by the cursor_position field. -func ByCursorPosition(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldCursorPosition, opts...).ToFunc() -} - // ByUserInput orders the results by the user_input field. func ByUserInput(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldUserInput, opts...).ToFunc() diff --git a/backend/db/task/where.go b/backend/db/task/where.go index 3096783..0931f9f 100644 --- a/backend/db/task/where.go +++ b/backend/db/task/where.go @@ -98,6 +98,11 @@ func WorkMode(v string) predicate.Task { return predicate.Task(sql.FieldEQ(FieldWorkMode, v)) } +// Prompt applies equality check predicate on the "prompt" field. It's identical to PromptEQ. +func Prompt(v string) predicate.Task { + return predicate.Task(sql.FieldEQ(FieldPrompt, v)) +} + // Completion applies equality check predicate on the "completion" field. It's identical to CompletionEQ. func Completion(v string) predicate.Task { return predicate.Task(sql.FieldEQ(FieldCompletion, v)) @@ -128,11 +133,6 @@ func SourceCode(v string) predicate.Task { return predicate.Task(sql.FieldEQ(FieldSourceCode, v)) } -// CursorPosition applies equality check predicate on the "cursor_position" field. It's identical to CursorPositionEQ. -func CursorPosition(v int64) predicate.Task { - return predicate.Task(sql.FieldEQ(FieldCursorPosition, v)) -} - // UserInput applies equality check predicate on the "user_input" field. It's identical to UserInputEQ. func UserInput(v string) predicate.Task { return predicate.Task(sql.FieldEQ(FieldUserInput, v)) @@ -592,6 +592,81 @@ func WorkModeContainsFold(v string) predicate.Task { return predicate.Task(sql.FieldContainsFold(FieldWorkMode, v)) } +// PromptEQ applies the EQ predicate on the "prompt" field. +func PromptEQ(v string) predicate.Task { + return predicate.Task(sql.FieldEQ(FieldPrompt, v)) +} + +// PromptNEQ applies the NEQ predicate on the "prompt" field. +func PromptNEQ(v string) predicate.Task { + return predicate.Task(sql.FieldNEQ(FieldPrompt, v)) +} + +// PromptIn applies the In predicate on the "prompt" field. +func PromptIn(vs ...string) predicate.Task { + return predicate.Task(sql.FieldIn(FieldPrompt, vs...)) +} + +// PromptNotIn applies the NotIn predicate on the "prompt" field. +func PromptNotIn(vs ...string) predicate.Task { + return predicate.Task(sql.FieldNotIn(FieldPrompt, vs...)) +} + +// PromptGT applies the GT predicate on the "prompt" field. +func PromptGT(v string) predicate.Task { + return predicate.Task(sql.FieldGT(FieldPrompt, v)) +} + +// PromptGTE applies the GTE predicate on the "prompt" field. +func PromptGTE(v string) predicate.Task { + return predicate.Task(sql.FieldGTE(FieldPrompt, v)) +} + +// PromptLT applies the LT predicate on the "prompt" field. +func PromptLT(v string) predicate.Task { + return predicate.Task(sql.FieldLT(FieldPrompt, v)) +} + +// PromptLTE applies the LTE predicate on the "prompt" field. +func PromptLTE(v string) predicate.Task { + return predicate.Task(sql.FieldLTE(FieldPrompt, v)) +} + +// PromptContains applies the Contains predicate on the "prompt" field. +func PromptContains(v string) predicate.Task { + return predicate.Task(sql.FieldContains(FieldPrompt, v)) +} + +// PromptHasPrefix applies the HasPrefix predicate on the "prompt" field. +func PromptHasPrefix(v string) predicate.Task { + return predicate.Task(sql.FieldHasPrefix(FieldPrompt, v)) +} + +// PromptHasSuffix applies the HasSuffix predicate on the "prompt" field. +func PromptHasSuffix(v string) predicate.Task { + return predicate.Task(sql.FieldHasSuffix(FieldPrompt, v)) +} + +// PromptIsNil applies the IsNil predicate on the "prompt" field. +func PromptIsNil() predicate.Task { + return predicate.Task(sql.FieldIsNull(FieldPrompt)) +} + +// PromptNotNil applies the NotNil predicate on the "prompt" field. +func PromptNotNil() predicate.Task { + return predicate.Task(sql.FieldNotNull(FieldPrompt)) +} + +// PromptEqualFold applies the EqualFold predicate on the "prompt" field. +func PromptEqualFold(v string) predicate.Task { + return predicate.Task(sql.FieldEqualFold(FieldPrompt, v)) +} + +// PromptContainsFold applies the ContainsFold predicate on the "prompt" field. +func PromptContainsFold(v string) predicate.Task { + return predicate.Task(sql.FieldContainsFold(FieldPrompt, v)) +} + // CompletionEQ applies the EQ predicate on the "completion" field. func CompletionEQ(v string) predicate.Task { return predicate.Task(sql.FieldEQ(FieldCompletion, v)) @@ -902,46 +977,6 @@ func SourceCodeContainsFold(v string) predicate.Task { return predicate.Task(sql.FieldContainsFold(FieldSourceCode, v)) } -// CursorPositionEQ applies the EQ predicate on the "cursor_position" field. -func CursorPositionEQ(v int64) predicate.Task { - return predicate.Task(sql.FieldEQ(FieldCursorPosition, v)) -} - -// CursorPositionNEQ applies the NEQ predicate on the "cursor_position" field. -func CursorPositionNEQ(v int64) predicate.Task { - return predicate.Task(sql.FieldNEQ(FieldCursorPosition, v)) -} - -// CursorPositionIn applies the In predicate on the "cursor_position" field. -func CursorPositionIn(vs ...int64) predicate.Task { - return predicate.Task(sql.FieldIn(FieldCursorPosition, vs...)) -} - -// CursorPositionNotIn applies the NotIn predicate on the "cursor_position" field. -func CursorPositionNotIn(vs ...int64) predicate.Task { - return predicate.Task(sql.FieldNotIn(FieldCursorPosition, vs...)) -} - -// CursorPositionGT applies the GT predicate on the "cursor_position" field. -func CursorPositionGT(v int64) predicate.Task { - return predicate.Task(sql.FieldGT(FieldCursorPosition, v)) -} - -// CursorPositionGTE applies the GTE predicate on the "cursor_position" field. -func CursorPositionGTE(v int64) predicate.Task { - return predicate.Task(sql.FieldGTE(FieldCursorPosition, v)) -} - -// CursorPositionLT applies the LT predicate on the "cursor_position" field. -func CursorPositionLT(v int64) predicate.Task { - return predicate.Task(sql.FieldLT(FieldCursorPosition, v)) -} - -// CursorPositionLTE applies the LTE predicate on the "cursor_position" field. -func CursorPositionLTE(v int64) predicate.Task { - return predicate.Task(sql.FieldLTE(FieldCursorPosition, v)) -} - // CursorPositionIsNil applies the IsNil predicate on the "cursor_position" field. func CursorPositionIsNil() predicate.Task { return predicate.Task(sql.FieldIsNull(FieldCursorPosition)) diff --git a/backend/db/task_create.go b/backend/db/task_create.go index b884c64..d09bd50 100644 --- a/backend/db/task_create.go +++ b/backend/db/task_create.go @@ -124,6 +124,20 @@ func (tc *TaskCreate) SetNillableWorkMode(s *string) *TaskCreate { return tc } +// SetPrompt sets the "prompt" field. +func (tc *TaskCreate) SetPrompt(s string) *TaskCreate { + tc.mutation.SetPrompt(s) + return tc +} + +// SetNillablePrompt sets the "prompt" field if the given value is not nil. +func (tc *TaskCreate) SetNillablePrompt(s *string) *TaskCreate { + if s != nil { + tc.SetPrompt(*s) + } + return tc +} + // SetCompletion sets the "completion" field. func (tc *TaskCreate) SetCompletion(s string) *TaskCreate { tc.mutation.SetCompletion(s) @@ -209,16 +223,8 @@ func (tc *TaskCreate) SetNillableSourceCode(s *string) *TaskCreate { } // SetCursorPosition sets the "cursor_position" field. -func (tc *TaskCreate) SetCursorPosition(i int64) *TaskCreate { - tc.mutation.SetCursorPosition(i) - return tc -} - -// SetNillableCursorPosition sets the "cursor_position" field if the given value is not nil. -func (tc *TaskCreate) SetNillableCursorPosition(i *int64) *TaskCreate { - if i != nil { - tc.SetCursorPosition(*i) - } +func (tc *TaskCreate) SetCursorPosition(m map[string]interface{}) *TaskCreate { + tc.mutation.SetCursorPosition(m) return tc } @@ -428,6 +434,10 @@ func (tc *TaskCreate) createSpec() (*Task, *sqlgraph.CreateSpec) { _spec.SetField(task.FieldWorkMode, field.TypeString, value) _node.WorkMode = value } + if value, ok := tc.mutation.Prompt(); ok { + _spec.SetField(task.FieldPrompt, field.TypeString, value) + _node.Prompt = value + } if value, ok := tc.mutation.Completion(); ok { _spec.SetField(task.FieldCompletion, field.TypeString, value) _node.Completion = value @@ -453,7 +463,7 @@ func (tc *TaskCreate) createSpec() (*Task, *sqlgraph.CreateSpec) { _node.SourceCode = value } if value, ok := tc.mutation.CursorPosition(); ok { - _spec.SetField(task.FieldCursorPosition, field.TypeInt64, value) + _spec.SetField(task.FieldCursorPosition, field.TypeJSON, value) _node.CursorPosition = value } if value, ok := tc.mutation.UserInput(); ok { @@ -696,6 +706,24 @@ func (u *TaskUpsert) ClearWorkMode() *TaskUpsert { return u } +// SetPrompt sets the "prompt" field. +func (u *TaskUpsert) SetPrompt(v string) *TaskUpsert { + u.Set(task.FieldPrompt, v) + return u +} + +// UpdatePrompt sets the "prompt" field to the value that was provided on create. +func (u *TaskUpsert) UpdatePrompt() *TaskUpsert { + u.SetExcluded(task.FieldPrompt) + return u +} + +// ClearPrompt clears the value of the "prompt" field. +func (u *TaskUpsert) ClearPrompt() *TaskUpsert { + u.SetNull(task.FieldPrompt) + return u +} + // SetCompletion sets the "completion" field. func (u *TaskUpsert) SetCompletion(v string) *TaskUpsert { u.Set(task.FieldCompletion, v) @@ -817,7 +845,7 @@ func (u *TaskUpsert) ClearSourceCode() *TaskUpsert { } // SetCursorPosition sets the "cursor_position" field. -func (u *TaskUpsert) SetCursorPosition(v int64) *TaskUpsert { +func (u *TaskUpsert) SetCursorPosition(v map[string]interface{}) *TaskUpsert { u.Set(task.FieldCursorPosition, v) return u } @@ -828,12 +856,6 @@ func (u *TaskUpsert) UpdateCursorPosition() *TaskUpsert { return u } -// AddCursorPosition adds v to the "cursor_position" field. -func (u *TaskUpsert) AddCursorPosition(v int64) *TaskUpsert { - u.Add(task.FieldCursorPosition, v) - return u -} - // ClearCursorPosition clears the value of the "cursor_position" field. func (u *TaskUpsert) ClearCursorPosition() *TaskUpsert { u.SetNull(task.FieldCursorPosition) @@ -1077,6 +1099,27 @@ func (u *TaskUpsertOne) ClearWorkMode() *TaskUpsertOne { }) } +// SetPrompt sets the "prompt" field. +func (u *TaskUpsertOne) SetPrompt(v string) *TaskUpsertOne { + return u.Update(func(s *TaskUpsert) { + s.SetPrompt(v) + }) +} + +// UpdatePrompt sets the "prompt" field to the value that was provided on create. +func (u *TaskUpsertOne) UpdatePrompt() *TaskUpsertOne { + return u.Update(func(s *TaskUpsert) { + s.UpdatePrompt() + }) +} + +// ClearPrompt clears the value of the "prompt" field. +func (u *TaskUpsertOne) ClearPrompt() *TaskUpsertOne { + return u.Update(func(s *TaskUpsert) { + s.ClearPrompt() + }) +} + // SetCompletion sets the "completion" field. func (u *TaskUpsertOne) SetCompletion(v string) *TaskUpsertOne { return u.Update(func(s *TaskUpsert) { @@ -1218,19 +1261,12 @@ func (u *TaskUpsertOne) ClearSourceCode() *TaskUpsertOne { } // SetCursorPosition sets the "cursor_position" field. -func (u *TaskUpsertOne) SetCursorPosition(v int64) *TaskUpsertOne { +func (u *TaskUpsertOne) SetCursorPosition(v map[string]interface{}) *TaskUpsertOne { return u.Update(func(s *TaskUpsert) { s.SetCursorPosition(v) }) } -// AddCursorPosition adds v to the "cursor_position" field. -func (u *TaskUpsertOne) AddCursorPosition(v int64) *TaskUpsertOne { - return u.Update(func(s *TaskUpsert) { - s.AddCursorPosition(v) - }) -} - // UpdateCursorPosition sets the "cursor_position" field to the value that was provided on create. func (u *TaskUpsertOne) UpdateCursorPosition() *TaskUpsertOne { return u.Update(func(s *TaskUpsert) { @@ -1656,6 +1692,27 @@ func (u *TaskUpsertBulk) ClearWorkMode() *TaskUpsertBulk { }) } +// SetPrompt sets the "prompt" field. +func (u *TaskUpsertBulk) SetPrompt(v string) *TaskUpsertBulk { + return u.Update(func(s *TaskUpsert) { + s.SetPrompt(v) + }) +} + +// UpdatePrompt sets the "prompt" field to the value that was provided on create. +func (u *TaskUpsertBulk) UpdatePrompt() *TaskUpsertBulk { + return u.Update(func(s *TaskUpsert) { + s.UpdatePrompt() + }) +} + +// ClearPrompt clears the value of the "prompt" field. +func (u *TaskUpsertBulk) ClearPrompt() *TaskUpsertBulk { + return u.Update(func(s *TaskUpsert) { + s.ClearPrompt() + }) +} + // SetCompletion sets the "completion" field. func (u *TaskUpsertBulk) SetCompletion(v string) *TaskUpsertBulk { return u.Update(func(s *TaskUpsert) { @@ -1797,19 +1854,12 @@ func (u *TaskUpsertBulk) ClearSourceCode() *TaskUpsertBulk { } // SetCursorPosition sets the "cursor_position" field. -func (u *TaskUpsertBulk) SetCursorPosition(v int64) *TaskUpsertBulk { +func (u *TaskUpsertBulk) SetCursorPosition(v map[string]interface{}) *TaskUpsertBulk { return u.Update(func(s *TaskUpsert) { s.SetCursorPosition(v) }) } -// AddCursorPosition adds v to the "cursor_position" field. -func (u *TaskUpsertBulk) AddCursorPosition(v int64) *TaskUpsertBulk { - return u.Update(func(s *TaskUpsert) { - s.AddCursorPosition(v) - }) -} - // UpdateCursorPosition sets the "cursor_position" field to the value that was provided on create. func (u *TaskUpsertBulk) UpdateCursorPosition() *TaskUpsertBulk { return u.Update(func(s *TaskUpsert) { diff --git a/backend/db/task_update.go b/backend/db/task_update.go index 650de88..1e75bd7 100644 --- a/backend/db/task_update.go +++ b/backend/db/task_update.go @@ -176,6 +176,26 @@ func (tu *TaskUpdate) ClearWorkMode() *TaskUpdate { return tu } +// SetPrompt sets the "prompt" field. +func (tu *TaskUpdate) SetPrompt(s string) *TaskUpdate { + tu.mutation.SetPrompt(s) + return tu +} + +// SetNillablePrompt sets the "prompt" field if the given value is not nil. +func (tu *TaskUpdate) SetNillablePrompt(s *string) *TaskUpdate { + if s != nil { + tu.SetPrompt(*s) + } + return tu +} + +// ClearPrompt clears the value of the "prompt" field. +func (tu *TaskUpdate) ClearPrompt() *TaskUpdate { + tu.mutation.ClearPrompt() + return tu +} + // SetCompletion sets the "completion" field. func (tu *TaskUpdate) SetCompletion(s string) *TaskUpdate { tu.mutation.SetCompletion(s) @@ -312,23 +332,8 @@ func (tu *TaskUpdate) ClearSourceCode() *TaskUpdate { } // SetCursorPosition sets the "cursor_position" field. -func (tu *TaskUpdate) SetCursorPosition(i int64) *TaskUpdate { - tu.mutation.ResetCursorPosition() - tu.mutation.SetCursorPosition(i) - return tu -} - -// SetNillableCursorPosition sets the "cursor_position" field if the given value is not nil. -func (tu *TaskUpdate) SetNillableCursorPosition(i *int64) *TaskUpdate { - if i != nil { - tu.SetCursorPosition(*i) - } - return tu -} - -// AddCursorPosition adds i to the "cursor_position" field. -func (tu *TaskUpdate) AddCursorPosition(i int64) *TaskUpdate { - tu.mutation.AddCursorPosition(i) +func (tu *TaskUpdate) SetCursorPosition(m map[string]interface{}) *TaskUpdate { + tu.mutation.SetCursorPosition(m) return tu } @@ -519,6 +524,12 @@ func (tu *TaskUpdate) sqlSave(ctx context.Context) (n int, err error) { if tu.mutation.WorkModeCleared() { _spec.ClearField(task.FieldWorkMode, field.TypeString) } + if value, ok := tu.mutation.Prompt(); ok { + _spec.SetField(task.FieldPrompt, field.TypeString, value) + } + if tu.mutation.PromptCleared() { + _spec.ClearField(task.FieldPrompt, field.TypeString) + } if value, ok := tu.mutation.Completion(); ok { _spec.SetField(task.FieldCompletion, field.TypeString, value) } @@ -562,13 +573,10 @@ func (tu *TaskUpdate) sqlSave(ctx context.Context) (n int, err error) { _spec.ClearField(task.FieldSourceCode, field.TypeString) } if value, ok := tu.mutation.CursorPosition(); ok { - _spec.SetField(task.FieldCursorPosition, field.TypeInt64, value) - } - if value, ok := tu.mutation.AddedCursorPosition(); ok { - _spec.AddField(task.FieldCursorPosition, field.TypeInt64, value) + _spec.SetField(task.FieldCursorPosition, field.TypeJSON, value) } if tu.mutation.CursorPositionCleared() { - _spec.ClearField(task.FieldCursorPosition, field.TypeInt64) + _spec.ClearField(task.FieldCursorPosition, field.TypeJSON) } if value, ok := tu.mutation.UserInput(); ok { _spec.SetField(task.FieldUserInput, field.TypeString, value) @@ -849,6 +857,26 @@ func (tuo *TaskUpdateOne) ClearWorkMode() *TaskUpdateOne { return tuo } +// SetPrompt sets the "prompt" field. +func (tuo *TaskUpdateOne) SetPrompt(s string) *TaskUpdateOne { + tuo.mutation.SetPrompt(s) + return tuo +} + +// SetNillablePrompt sets the "prompt" field if the given value is not nil. +func (tuo *TaskUpdateOne) SetNillablePrompt(s *string) *TaskUpdateOne { + if s != nil { + tuo.SetPrompt(*s) + } + return tuo +} + +// ClearPrompt clears the value of the "prompt" field. +func (tuo *TaskUpdateOne) ClearPrompt() *TaskUpdateOne { + tuo.mutation.ClearPrompt() + return tuo +} + // SetCompletion sets the "completion" field. func (tuo *TaskUpdateOne) SetCompletion(s string) *TaskUpdateOne { tuo.mutation.SetCompletion(s) @@ -985,23 +1013,8 @@ func (tuo *TaskUpdateOne) ClearSourceCode() *TaskUpdateOne { } // SetCursorPosition sets the "cursor_position" field. -func (tuo *TaskUpdateOne) SetCursorPosition(i int64) *TaskUpdateOne { - tuo.mutation.ResetCursorPosition() - tuo.mutation.SetCursorPosition(i) - return tuo -} - -// SetNillableCursorPosition sets the "cursor_position" field if the given value is not nil. -func (tuo *TaskUpdateOne) SetNillableCursorPosition(i *int64) *TaskUpdateOne { - if i != nil { - tuo.SetCursorPosition(*i) - } - return tuo -} - -// AddCursorPosition adds i to the "cursor_position" field. -func (tuo *TaskUpdateOne) AddCursorPosition(i int64) *TaskUpdateOne { - tuo.mutation.AddCursorPosition(i) +func (tuo *TaskUpdateOne) SetCursorPosition(m map[string]interface{}) *TaskUpdateOne { + tuo.mutation.SetCursorPosition(m) return tuo } @@ -1222,6 +1235,12 @@ func (tuo *TaskUpdateOne) sqlSave(ctx context.Context) (_node *Task, err error) if tuo.mutation.WorkModeCleared() { _spec.ClearField(task.FieldWorkMode, field.TypeString) } + if value, ok := tuo.mutation.Prompt(); ok { + _spec.SetField(task.FieldPrompt, field.TypeString, value) + } + if tuo.mutation.PromptCleared() { + _spec.ClearField(task.FieldPrompt, field.TypeString) + } if value, ok := tuo.mutation.Completion(); ok { _spec.SetField(task.FieldCompletion, field.TypeString, value) } @@ -1265,13 +1284,10 @@ func (tuo *TaskUpdateOne) sqlSave(ctx context.Context) (_node *Task, err error) _spec.ClearField(task.FieldSourceCode, field.TypeString) } if value, ok := tuo.mutation.CursorPosition(); ok { - _spec.SetField(task.FieldCursorPosition, field.TypeInt64, value) - } - if value, ok := tuo.mutation.AddedCursorPosition(); ok { - _spec.AddField(task.FieldCursorPosition, field.TypeInt64, value) + _spec.SetField(task.FieldCursorPosition, field.TypeJSON, value) } if tuo.mutation.CursorPositionCleared() { - _spec.ClearField(task.FieldCursorPosition, field.TypeInt64) + _spec.ClearField(task.FieldCursorPosition, field.TypeJSON) } if value, ok := tuo.mutation.UserInput(); ok { _spec.SetField(task.FieldUserInput, field.TypeString, value) diff --git a/backend/domain/proxy.go b/backend/domain/proxy.go index 6142805..2d51a22 100644 --- a/backend/domain/proxy.go +++ b/backend/domain/proxy.go @@ -51,7 +51,7 @@ type ReportReq struct { Tool string `json:"tool"` // 工具 UserInput string `json:"user_input"` // 用户输入的新文本(用于reject action) SourceCode string `json:"source_code"` // 当前文件的原文(用于reject action) - CursorPosition int64 `json:"cursor_position"` // 光标位置(用于reject action) + CursorPosition map[string]any `json:"cursor_position"` // 光标位置(用于reject action) } type RecordParam struct { @@ -70,9 +70,9 @@ type RecordParam struct { WorkMode string CodeLines int64 Code string - SourceCode string // 当前文件的原文 - CursorPosition int64 // 光标位置 - UserInput string // 用户实际输入的内容 + SourceCode string // 当前文件的原文 + CursorPosition map[string]any // 光标位置 + UserInput string // 用户实际输入的内容 } func (r *RecordParam) Clone() *RecordParam { diff --git a/backend/domain/user.go b/backend/domain/user.go index 52a9342..e1f01b6 100644 --- a/backend/domain/user.go +++ b/backend/domain/user.go @@ -31,6 +31,7 @@ type UserUsecase interface { UpdateSetting(ctx context.Context, req *UpdateSettingReq) (*Setting, error) OAuthSignUpOrIn(ctx context.Context, req *OAuthSignUpOrInReq) (*OAuthURLResp, error) OAuthCallback(ctx *web.Context, req *OAuthCallbackReq) error + ExportCompletionData(ctx context.Context) (*ExportCompletionDataResp, error) } type UserRepo interface { @@ -56,6 +57,7 @@ type UserRepo interface { SignUpOrIn(ctx context.Context, platform consts.UserPlatform, req *OAuthUserInfo) (*db.User, error) SaveAdminLoginHistory(ctx context.Context, adminID, ip string) error SaveUserLoginHistory(ctx context.Context, userID, ip string, session *VSCodeSession) error + ExportCompletionData(ctx context.Context) ([]*CompletionData, error) } type ProfileUpdateReq struct { @@ -388,3 +390,33 @@ func (s *Setting) From(e *db.Setting) *Setting { return s } + +// CompletionData 补全数据导出结构 +type CompletionData struct { + TaskID string `json:"task_id"` // 任务ID + UserID string `json:"user_id"` // 用户ID + ModelID string `json:"model_id"` // 模型ID + ModelName string `json:"model_name"` // 模型名称 + RequestID string `json:"request_id"` // 请求ID + ModelType string `json:"model_type"` // 模型类型 + ProgramLanguage string `json:"program_language"` // 编程语言 + WorkMode string `json:"work_mode"` // 工作模式 + Prompt string `json:"prompt"` // 用户输入的提示 + Completion string `json:"completion"` // LLM生成的补全代码 + SourceCode string `json:"source_code"` // 当前文件原文 + CursorPosition map[string]any `json:"cursor_position"` // 光标位置 {"line": 10, "column": 5} + UserInput string `json:"user_input"` // 用户最终输入的内容 + IsAccept bool `json:"is_accept"` // 用户是否接受补全 + IsSuggested bool `json:"is_suggested"` // 是否为建议模式 + CodeLines int64 `json:"code_lines"` // 代码行数 + InputTokens int64 `json:"input_tokens"` // 输入token数 + OutputTokens int64 `json:"output_tokens"` // 输出token数 + CreatedAt int64 `json:"created_at"` // 创建时间戳 + UpdatedAt int64 `json:"updated_at"` // 更新时间戳 +} + +// ExportCompletionDataResp 导出补全数据响应 +type ExportCompletionDataResp struct { + TotalCount int64 `json:"total_count"` // 总记录数 + Data []*CompletionData `json:"data"` // 补全数据列表 +} diff --git a/backend/ent/schema/task.go b/backend/ent/schema/task.go index cf967b2..9395049 100644 --- a/backend/ent/schema/task.go +++ b/backend/ent/schema/task.go @@ -37,14 +37,15 @@ func (Task) Fields() []ent.Field { field.Bool("is_accept").Default(false), field.String("program_language").Optional(), field.String("work_mode").Optional(), + field.String("prompt").Optional(), // 用户输入的提示 field.String("completion").Optional(), field.Int64("code_lines").Optional(), field.Int64("input_tokens").Optional(), field.Int64("output_tokens").Optional(), field.Bool("is_suggested").Default(false), - field.String("source_code").Optional(), // 当前文件的原文 - field.Int64("cursor_position").Optional(), // 光标位置 - field.String("user_input").Optional(), // 用户实际输入的内容 + field.String("source_code").Optional(), // 当前文件的原文 + field.JSON("cursor_position", map[string]any{}).Optional(), // 光标位置 {"line": 10, "column": 5} + field.String("user_input").Optional(), // 用户实际输入的内容 field.Time("created_at").Default(time.Now), field.Time("updated_at").Default(time.Now).UpdateDefault(time.Now), } diff --git a/backend/internal/proxy/recorder.go b/backend/internal/proxy/recorder.go index c48fd68..18492f7 100644 --- a/backend/internal/proxy/recorder.go +++ b/backend/internal/proxy/recorder.go @@ -69,7 +69,7 @@ func (r *Recorder) handleShadow() { var ( taskID, mode, prompt, language, tool, code, sourceCode, userInput string - cursorPosition int64 + cursorPosition map[string]any ) switch r.ctx.Model.ModelType { @@ -96,8 +96,15 @@ func (r *Recorder) handleShadow() { mode = req.Metadata["mode"] language = req.Metadata["program_language"] sourceCode = req.Metadata["source_code"] - if pos, err := strconv.ParseInt(req.Metadata["cursor_position"], 10, 64); err == nil { - cursorPosition = pos + // 解析cursor_position为JSON格式 + if posStr := req.Metadata["cursor_position"]; posStr != "" { + if pos, err := strconv.ParseInt(posStr, 10, 64); err == nil { + cursorPosition = map[string]any{ + "position": pos, + "line": 1, // 默认值 + "column": pos, + } + } } userInput = req.Metadata["user_input"] diff --git a/backend/internal/proxy/repo/proxy.go b/backend/internal/proxy/repo/proxy.go index 52c7fa2..552dcfb 100644 --- a/backend/internal/proxy/repo/proxy.go +++ b/backend/internal/proxy/repo/proxy.go @@ -100,6 +100,7 @@ func (r *ProxyRepo) Record(ctx context.Context, record *domain.RecordParam) erro SetIsAccept(record.IsAccept). SetModelType(record.ModelType). SetWorkMode(record.WorkMode). + SetPrompt(record.Prompt). SetCodeLines(record.CodeLines). SetSourceCode(record.SourceCode). SetCursorPosition(record.CursorPosition). @@ -126,7 +127,10 @@ func (r *ProxyRepo) Record(ctx context.Context, record *domain.RecordParam) erro if record.SourceCode != "" { up.SetSourceCode(record.SourceCode) } - if record.CursorPosition > 0 { + if record.Prompt != "" { + up.SetPrompt(record.Prompt) + } + if record.CursorPosition != nil { up.SetCursorPosition(record.CursorPosition) } if record.UserInput != "" { @@ -264,10 +268,16 @@ func (r *ProxyRepo) handleRejectCompletion(ctx context.Context, tx *db.Tx, rc *d shouldRecord := false // 检查光标位置是否匹配(允许小的误差) - if req.CursorPosition > 0 && rc.CursorPosition > 0 { - positionDiff := abs(req.CursorPosition - rc.CursorPosition) - if positionDiff <= 10 { // 允许10个字符的误差 - shouldRecord = true + if req.CursorPosition != nil && rc.CursorPosition != nil { + // 从JSON中提取position值进行比较 + reqPos, reqOk := req.CursorPosition["position"].(float64) + rcPos, rcOk := rc.CursorPosition["position"].(float64) + + if reqOk && rcOk { + positionDiff := abs(int64(reqPos) - int64(rcPos)) + if positionDiff <= 10 { // 允许10个字符的误差 + shouldRecord = true + } } } diff --git a/backend/internal/user/handler/v1/user.go b/backend/internal/user/handler/v1/user.go index bc6db63..f64b7ee 100644 --- a/backend/internal/user/handler/v1/user.go +++ b/backend/internal/user/handler/v1/user.go @@ -83,6 +83,7 @@ func NewUserHandler( admin.POST("/create", web.BindHandler(u.CreateAdmin)) admin.POST("/logout", web.BaseHandler(u.AdminLogout)) admin.DELETE("/delete", web.BaseHandler(u.DeleteAdmin)) + admin.GET("/export-completion-data", web.BaseHandler(u.ExportCompletionData)) // user g := w.Group("/api/v1/user") @@ -620,3 +621,23 @@ func (h *UserHandler) UpdateProfile(ctx *web.Context, req domain.ProfileUpdateRe func (h *UserHandler) InitAdmin() error { return h.usecase.InitAdmin(context.Background()) } + +// ExportCompletionData godoc +// @Summary 导出补全数据 +// @Description 管理员导出所有补全相关数据 +// @Tags admin +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Success 200 {object} domain.ExportCompletionDataResp +// @Failure 401 {object} errcode.Error +// @Failure 500 {object} errcode.Error +// @Router /api/v1/admin/export-completion-data [get] +func (h *UserHandler) ExportCompletionData(c *web.Context) error { + data, err := h.usecase.ExportCompletionData(c.Request().Context()) + if err != nil { + return err + } + + return c.JSON(http.StatusOK, data) +} diff --git a/backend/internal/user/repo/user.go b/backend/internal/user/repo/user.go index 908c0db..8bca44e 100644 --- a/backend/internal/user/repo/user.go +++ b/backend/internal/user/repo/user.go @@ -18,6 +18,7 @@ import ( "github.com/chaitin/MonkeyCode/backend/db/adminloginhistory" "github.com/chaitin/MonkeyCode/backend/db/apikey" "github.com/chaitin/MonkeyCode/backend/db/invitecode" + "github.com/chaitin/MonkeyCode/backend/db/model" "github.com/chaitin/MonkeyCode/backend/db/user" "github.com/chaitin/MonkeyCode/backend/db/useridentity" "github.com/chaitin/MonkeyCode/backend/db/userloginhistory" @@ -437,3 +438,74 @@ func (r *UserRepo) SaveUserLoginHistory(ctx context.Context, userID string, ip s return c.Exec(ctx) } + +func (r *UserRepo) ExportCompletionData(ctx context.Context) ([]*domain.CompletionData, error) { + // 查询所有任务数据 + tasks, err := r.db.Task.Query().All(ctx) + if err != nil { + return nil, err + } + + // 获取所有模型ID并查询模型信息 + modelIDs := make([]uuid.UUID, 0) + for _, t := range tasks { + if t.ModelID != uuid.Nil { + modelIDs = append(modelIDs, t.ModelID) + } + } + + models, err := r.db.Model.Query().Where(model.IDIn(modelIDs...)).All(ctx) + if err != nil { + return nil, err + } + + // 创建模型ID到模型名称的映射 + modelMap := make(map[uuid.UUID]string) + for _, m := range models { + modelMap[m.ID] = m.ShowName + } + + var result []*domain.CompletionData + for _, t := range tasks { + // 获取模型名称 + modelName := "" + if t.ModelID != uuid.Nil { + if name, exists := modelMap[t.ModelID]; exists { + modelName = name + } + } + + // 处理cursor_position(已经是JSON格式) + var cursorPosition map[string]any + if t.CursorPosition != nil { + cursorPosition = t.CursorPosition + } + + completionData := &domain.CompletionData{ + TaskID: t.TaskID, + UserID: t.UserID.String(), + ModelID: t.ModelID.String(), + ModelName: modelName, + RequestID: t.RequestID, + ModelType: string(t.ModelType), + ProgramLanguage: t.ProgramLanguage, + WorkMode: t.WorkMode, + Prompt: t.Prompt, + Completion: t.Completion, + SourceCode: t.SourceCode, + CursorPosition: cursorPosition, + UserInput: t.UserInput, + IsAccept: t.IsAccept, + IsSuggested: t.IsSuggested, + CodeLines: t.CodeLines, + InputTokens: t.InputTokens, + OutputTokens: t.OutputTokens, + CreatedAt: t.CreatedAt.Unix(), + UpdatedAt: t.UpdatedAt.Unix(), + } + + result = append(result, completionData) + } + + return result, nil +} diff --git a/backend/internal/user/usecase/user.go b/backend/internal/user/usecase/user.go index b0bc59c..216d29c 100644 --- a/backend/internal/user/usecase/user.go +++ b/backend/internal/user/usecase/user.go @@ -635,3 +635,15 @@ func (u *UserUsecase) ProfileUpdate(ctx context.Context, req *domain.ProfileUpda } return cvt.From(user, &domain.User{}), nil } + +func (u *UserUsecase) ExportCompletionData(ctx context.Context) (*domain.ExportCompletionDataResp, error) { + data, err := u.repo.ExportCompletionData(ctx) + if err != nil { + return nil, err + } + + return &domain.ExportCompletionDataResp{ + TotalCount: int64(len(data)), + Data: data, + }, nil +} diff --git a/backend/migration/000010_alter_task_table.down.sql b/backend/migration/000010_alter_task_table.down.sql new file mode 100644 index 0000000..b6871ff --- /dev/null +++ b/backend/migration/000010_alter_task_table.down.sql @@ -0,0 +1,10 @@ +-- 回滚cursor_position类型修改 +ALTER TABLE tasks ALTER COLUMN cursor_position TYPE bigint USING + CASE + WHEN cursor_position IS NOT NULL AND cursor_position ? 'position' THEN + (cursor_position->>'position')::bigint + ELSE NULL + END; + +-- 删除prompt字段 +ALTER TABLE tasks DROP COLUMN IF EXISTS prompt; diff --git a/backend/migration/000010_alter_task_table.up.sql b/backend/migration/000010_alter_task_table.up.sql new file mode 100644 index 0000000..e2bb29b --- /dev/null +++ b/backend/migration/000010_alter_task_table.up.sql @@ -0,0 +1,10 @@ +-- 添加prompt字段(如果不存在) +ALTER TABLE tasks ADD COLUMN IF NOT EXISTS prompt text; + +-- 修改cursor_position为JSON类型(如果还是bigint类型) +ALTER TABLE tasks ALTER COLUMN cursor_position TYPE jsonb USING + CASE + WHEN cursor_position IS NOT NULL THEN + jsonb_build_object('position', cursor_position, 'line', 1, 'column', cursor_position) + ELSE NULL + END;