| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- package routes
-
- import (
- "context"
- "strings"
-
- "git.x2erp.com/qdy/go-base/authbase"
- "git.x2erp.com/qdy/go-base/ctx"
- "git.x2erp.com/qdy/go-base/logger"
- "git.x2erp.com/qdy/go-base/model/response"
- "git.x2erp.com/qdy/go-base/util"
- "git.x2erp.com/qdy/go-base/webx/router"
- "git.x2erp.com/qdy/go-svc-code/internal/model"
- "git.x2erp.com/qdy/go-svc-code/internal/opencode"
- "git.x2erp.com/qdy/go-svc-code/internal/service"
- )
-
- // SessionCreateRequest 创建会话请求
- type SessionCreateRequest struct {
- Title string `json:"title" binding:"required"`
- AgentName string `json:"agent_name" binding:"required"` // 智能体名称
- ProjectID string `json:"project_id,omitempty"` // 项目ID(新增,前端生成proj_前缀UUID)
- Description string `json:"description,omitempty"` // 项目描述(新增)
- }
-
- // SessionUpdateRequest 更新会话请求
- type SessionUpdateRequest struct {
- Title string `json:"title,omitempty"`
- Status string `json:"status,omitempty"`
- }
-
- // SessionListQuery 会话列表查询参数
- type SessionListQuery struct {
- Page int `form:"page" binding:"min=1"` // 页码,从1开始
- PageSize int `form:"pageSize" binding:"min=1,max=100"` // 每页大小,最大100
- Title string `form:"title,omitempty"` // 标题搜索(模糊匹配)
- Status string `form:"status,omitempty"` // 状态筛选
- }
-
- // RegisterSessionRoutes 注册会话管理路由
- func RegisterSessionRoutes(ws *router.RouterService, client opencode.OpenCodeClient, sessionStore *service.SessionStore, detailStore *service.SessionDetailStore) {
- // 创建会话(需要Token认证)
- ws.POST("/api/session/create",
- func(req *SessionCreateRequest, ctx context.Context, reqCtx *ctx.RequestContext) (*response.QueryResult[*model.Session], error) {
- // 从认证信息中获取用户ID和租户ID
- userID := reqCtx.UserID
- tenantID := reqCtx.TenantID
- if userID == "" {
- userID = "unknown_user"
- logger.Warn("无法从请求上下文获取用户ID,使用默认值")
- }
- if tenantID == "" {
- tenantID = "default"
- }
-
- // 1. 验证智能体名称是否有效
- if !model.IsValidAgent(req.AgentName) {
- // 构建支持智能体列表的错误信息
- supportedAgents := ""
- for id, name := range model.AgentDisplayName {
- supportedAgents += id + "(" + name + ")、"
- }
- // 移除最后一个"、"
- if len(supportedAgents) > 0 {
- supportedAgents = supportedAgents[:len(supportedAgents)-len("、")]
- }
- return &response.QueryResult[*model.Session]{
- Success: false,
- Message: "无效的智能体名称,支持的智能体有:" + supportedAgents,
- }, nil
- }
-
- // 2. 首先使用opencodeapi创建一个会话ID
- session, err := client.CreateSession(ctx, req.Title)
- if err != nil {
- logger.Error("创建opencode会话失败", "title", req.Title, "error", err)
- return &response.QueryResult[*model.Session]{
- Success: false,
- Message: "创建会话失败: " + err.Error(),
- }, nil
- }
-
- // 2. 保存到MongoDB
- projectID := req.ProjectID
- if projectID == "" {
- // 向后兼容:如果没有提供项目ID,使用会话ID作为项目ID
- projectID = session.ID
- }
- dbSession := &model.Session{
- ID: session.ID,
- ProjectID: projectID,
- Title: req.Title,
- AgentName: req.AgentName,
- Description: req.Description,
- Status: model.StatusRequirementDocument, // 默认状态为需求文档
- UserID: userID,
- TenantID: tenantID,
- }
-
- if err := sessionStore.Create(ctx, dbSession); err != nil {
- logger.Error("保存会话到数据库失败", "session_id", session.ID, "error", err)
- // 注意:opencode会话已创建,但数据库保存失败
- // 可以考虑回滚(删除opencode会话),但先记录错误
- return &response.QueryResult[*model.Session]{
- Success: false,
- Message: "保存会话信息失败: " + err.Error(),
- }, nil
- }
-
- // 3. 创建空的明细文档
- detail := &model.SessionDetail{
- SessionID: session.ID,
- RequirementDoc: "",
- TechnicalDoc: "",
- CodeItems: []model.CodeItem{},
- HistorySessions: []string{session.ID}, // 初始化历史会话数组
- }
-
- if err := detailStore.Create(ctx, detail); err != nil {
- logger.Warn("创建会话明细失败", "session_id", session.ID, "error", err)
- // 明细创建失败不影响主流程,但记录警告
- }
-
- logger.Debug("创建会话成功", "session_id", session.ID, "title", req.Title, "agent", req.AgentName)
- return util.CreateSuccessResultData(dbSession, reqCtx), nil
- },
- ).Use(authbase.TokenAuth).Desc("创建新的会话").Register()
-
- // 分页查询会话列表(需要Token认证)
- ws.GET("/api/sessions",
- func(query *SessionListQuery, ctx context.Context, reqCtx *ctx.RequestContext) (*response.QueryResult[*service.ListResult], error) {
- // 处理nil查询参数
- if query == nil {
- logger.Warn("查询参数为nil,使用默认值")
- query = &SessionListQuery{
- Page: 1,
- PageSize: 20,
- }
- }
- // 设置默认值
- if query.Page == 0 {
- query.Page = 1
- }
- if query.PageSize == 0 {
- query.PageSize = 20
- }
-
- // 从认证信息中获取用户ID和租户ID
- userID := reqCtx.UserID
- tenantID := reqCtx.TenantID
- if userID == "" {
- return &response.QueryResult[*service.ListResult]{
- Success: false,
- Message: "用户认证信息不完整",
- }, nil
- }
- if tenantID == "" {
- tenantID = "default"
- }
-
- // 构建查询参数
- listQuery := &service.ListQuery{
- Page: query.Page,
- PageSize: query.PageSize,
- Title: strings.TrimSpace(query.Title),
- Status: strings.TrimSpace(query.Status),
- UserID: userID,
- TenantID: tenantID,
- }
-
- // 查询会话列表
- result, err := sessionStore.List(ctx, listQuery)
- if err != nil {
- logger.Error("查询会话列表失败", "error", err)
- return &response.QueryResult[*service.ListResult]{
- Success: false,
- Message: "查询会话列表失败: " + err.Error(),
- }, nil
- }
-
- return util.CreateSuccessResultData(result, reqCtx), nil
- },
- ).Use(authbase.TokenAuth).Desc("分页查询会话列表,支持按标题搜索和按状态筛选").Register()
-
- // 获取单个会话详情(需要Token认证)
- ws.GET("/api/session/{id}",
- func(id string, ctx context.Context, reqCtx *ctx.RequestContext) (*response.QueryResult[*model.SessionWithDetail], error) {
- if id == "" {
- return &response.QueryResult[*model.SessionWithDetail]{
- Success: false,
- Message: "参数 id 不能为空",
- }, nil
- }
-
- // 1. 获取主会话
- session, err := sessionStore.GetByID(ctx, id)
- if err != nil {
- logger.Error("获取会话失败", "session_id", id, "error", err)
- return &response.QueryResult[*model.SessionWithDetail]{
- Success: false,
- Message: "获取会话失败: " + err.Error(),
- }, nil
- }
- if session == nil {
- return &response.QueryResult[*model.SessionWithDetail]{
- Success: false,
- Message: "会话不存在",
- }, nil
- }
-
- // 2. 获取明细文档
- detail, err := detailStore.GetBySessionID(ctx, id)
- if err != nil {
- logger.Error("获取会话明细失败", "session_id", id, "error", err)
- return &response.QueryResult[*model.SessionWithDetail]{
- Success: false,
- Message: "获取会话明细失败: " + err.Error(),
- }, nil
- }
-
- // 3. 获取历史会话上下文(从opencode获取)
- var historyCount int
- messages, err := client.GetSessionMessages(ctx, id, 50) // 获取最近50条消息
- if err != nil {
- logger.Warn("获取会话历史消息失败", "session_id", id, "error", err)
- // 历史消息获取失败不影响主流程,继续返回其他数据
- } else {
- historyCount = len(messages)
- }
-
- // 4. 组合响应
- result := &model.SessionWithDetail{
- Session: session,
- Detail: detail,
- HistoryCount: historyCount,
- }
-
- return util.CreateSuccessResultData(result, reqCtx), nil
- },
- ).Use(authbase.TokenAuth).Desc("获取会话详情(包括主对象、明细文档和历史消息数量)").Register()
-
- // 更新会话(状态和标题)(需要Token认证)
- ws.PUT("/api/session/{id}",
- func(id string, req *SessionUpdateRequest, ctx context.Context, reqCtx *ctx.RequestContext) (*response.QueryResult[bool], error) {
- if id == "" {
- return &response.QueryResult[bool]{
- Success: false,
- Message: "参数 id 不能为空",
- }, nil
- }
-
- // 验证请求数据
- if req.Title == "" && req.Status == "" {
- return &response.QueryResult[bool]{
- Success: false,
- Message: "至少需要提供标题或状态中的一个",
- }, nil
- }
-
- // 更新会话
- if err := sessionStore.Update(ctx, id, req.Title, req.Status); err != nil {
- logger.Error("更新会话失败", "session_id", id, "error", err)
- return &response.QueryResult[bool]{
- Success: false,
- Message: "更新会话失败: " + err.Error(),
- }, nil
- }
-
- return util.CreateSuccessResultData(true, reqCtx), nil
- },
- ).Use(authbase.TokenAuth).Desc("更新会话状态和标题(已发布的会话不能修改)").Register()
-
- // 删除会话(需要Token认证)
- ws.DELETE("/api/session/{id}",
- func(id string, ctx context.Context, reqCtx *ctx.RequestContext) (*response.QueryResult[bool], error) {
- if id == "" {
- return &response.QueryResult[bool]{
- Success: false,
- Message: "参数 id 不能为空",
- }, nil
- }
-
- // 1. 从MongoDB删除主会话
- if err := sessionStore.Delete(ctx, id); err != nil {
- logger.Error("删除会话失败", "session_id", id, "error", err)
- return &response.QueryResult[bool]{
- Success: false,
- Message: "删除会话失败: " + err.Error(),
- }, nil
- }
-
- // 2. 删除明细文档
- if err := detailStore.DeleteBySessionID(ctx, id); err != nil {
- logger.Warn("删除会话明细失败", "session_id", id, "error", err)
- // 明细删除失败不影响主流程,但记录警告
- }
-
- // 3. 调用opencode-api删除会话
- // 注意:opencode API可能不支持直接删除会话,这里暂时记录日志
- logger.Debug("需要调用opencode-api删除会话", "session_id", id)
- // TODO: 实现opencode会话删除
- // if err := client.DeleteSession(ctx, id); err != nil {
- // logger.Warn("删除opencode会话失败", "session_id", id, "error", err)
- // }
-
- return util.CreateSuccessResultData(true, reqCtx), nil
- },
- ).Use(authbase.TokenAuth).Desc("删除会话(同时删除明细文档,已发布的会话不能删除)").Register()
- }
|