feat(ai): add local expert library
This commit is contained in:
@@ -15,18 +15,38 @@ import (
|
||||
|
||||
// SessionDTO 是普通项目 AI 会话的公开契约,不携带数据库主键或自动创建对象的 ID。
|
||||
type SessionDTO struct {
|
||||
ID string `json:"id"`
|
||||
ProjectID string `json:"projectId"`
|
||||
Title string `json:"title"`
|
||||
Context string `json:"context"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
ID string `json:"id"`
|
||||
ProjectID string `json:"projectId"`
|
||||
Title string `json:"title"`
|
||||
Context string `json:"context"`
|
||||
Status string `json:"status"`
|
||||
Expert *ExpertSummaryDTO `json:"expert"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type createSessionRequest struct {
|
||||
Title string `json:"title"`
|
||||
Context string `json:"context"`
|
||||
Title string `json:"title"`
|
||||
Context string `json:"context"`
|
||||
ExpertID string `json:"expertId"`
|
||||
}
|
||||
|
||||
type ExpertSummaryDTO struct {
|
||||
ID string `json:"id"`
|
||||
Slug string `json:"slug"`
|
||||
Category string `json:"category"`
|
||||
CategoryName string `json:"categoryName"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Emoji string `json:"emoji"`
|
||||
Color string `json:"color"`
|
||||
}
|
||||
|
||||
type ExpertDetailDTO struct {
|
||||
ExpertSummaryDTO
|
||||
SystemPrompt string `json:"systemPrompt"`
|
||||
Source string `json:"source"`
|
||||
SourceLicense string `json:"sourceLicense"`
|
||||
}
|
||||
|
||||
// Handler 注册受认证、受项目所有权保护的 AI 会话接口。
|
||||
@@ -39,6 +59,8 @@ func NewHandler(service *SessionService) *Handler {
|
||||
}
|
||||
|
||||
func (h *Handler) Register(router gin.IRouter) {
|
||||
router.GET("/ai-experts", h.listExperts)
|
||||
router.GET("/ai-experts/:id", h.getExpert)
|
||||
router.GET("/projects/:id/ai-sessions", h.list)
|
||||
router.POST("/projects/:id/ai-sessions", h.create)
|
||||
}
|
||||
@@ -70,7 +92,7 @@ func (h *Handler) create(c *gin.Context) {
|
||||
httpx.Error(c, http.StatusBadRequest, "invalid_request", "请求参数无效")
|
||||
return
|
||||
}
|
||||
session, err := h.service.Create(userID, projectIdentity, input.Title, input.Context)
|
||||
session, err := h.service.CreateWithExpert(userID, projectIdentity, input.Title, input.Context, input.ExpertID)
|
||||
if err != nil {
|
||||
writeAIError(c, err)
|
||||
return
|
||||
@@ -78,6 +100,35 @@ func (h *Handler) create(c *gin.Context) {
|
||||
c.JSON(http.StatusCreated, sessionDTO(*session))
|
||||
}
|
||||
|
||||
func (h *Handler) listExperts(c *gin.Context) {
|
||||
experts, err := h.service.ListExperts(ExpertFilter{Category: c.Query("category"), Query: c.Query("q")})
|
||||
if err != nil {
|
||||
writeAIError(c, err)
|
||||
return
|
||||
}
|
||||
items := make([]ExpertSummaryDTO, 0, len(experts))
|
||||
for _, expert := range experts {
|
||||
items = append(items, expertSummaryDTO(expert))
|
||||
}
|
||||
c.JSON(http.StatusOK, items)
|
||||
}
|
||||
|
||||
func (h *Handler) getExpert(c *gin.Context) {
|
||||
identity, ok := httpx.IdentityParam(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
expert, err := h.service.GetExpert(identity)
|
||||
if err != nil {
|
||||
writeAIError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, ExpertDetailDTO{
|
||||
ExpertSummaryDTO: expertSummaryDTO(*expert),
|
||||
SystemPrompt: expert.SystemPrompt, Source: expert.Source, SourceLicense: expert.SourceLicense,
|
||||
})
|
||||
}
|
||||
|
||||
func aiRequestContext(c *gin.Context) (uint, string, bool) {
|
||||
userID, ok := auth.CurrentUserID(c)
|
||||
if !ok {
|
||||
@@ -92,7 +143,7 @@ func aiRequestContext(c *gin.Context) (uint, string, bool) {
|
||||
}
|
||||
|
||||
func sessionDTO(session models.SenlinAgentAISession) SessionDTO {
|
||||
return SessionDTO{
|
||||
dto := SessionDTO{
|
||||
ID: session.Identity,
|
||||
ProjectID: session.ProjectIdentity,
|
||||
Title: session.Title,
|
||||
@@ -101,10 +152,29 @@ func sessionDTO(session models.SenlinAgentAISession) SessionDTO {
|
||||
CreatedAt: session.CreatedAt.UTC(),
|
||||
UpdatedAt: session.UpdatedAt.UTC(),
|
||||
}
|
||||
if session.Expert != nil {
|
||||
dto.Expert = expertSummaryPointer(*session.Expert)
|
||||
}
|
||||
return dto
|
||||
}
|
||||
|
||||
func expertSummaryPointer(expert models.SenlinAgentAIExpert) *ExpertSummaryDTO {
|
||||
dto := expertSummaryDTO(expert)
|
||||
return &dto
|
||||
}
|
||||
|
||||
func expertSummaryDTO(expert models.SenlinAgentAIExpert) ExpertSummaryDTO {
|
||||
return ExpertSummaryDTO{
|
||||
ID: expert.Identity, Slug: expert.Slug, Category: expert.Category,
|
||||
CategoryName: expert.CategoryName, Name: expert.Name, Description: expert.Description,
|
||||
Emoji: expert.Emoji, Color: expert.Color,
|
||||
}
|
||||
}
|
||||
|
||||
func writeAIError(c *gin.Context, err error) {
|
||||
switch {
|
||||
case errors.Is(err, ErrExpertNotFound):
|
||||
httpx.Error(c, http.StatusNotFound, "expert_not_found", "专家不存在或已停用")
|
||||
case errors.Is(err, gorm.ErrRecordNotFound):
|
||||
httpx.Error(c, http.StatusNotFound, "not_found", "项目不存在或无权访问")
|
||||
case errors.Is(err, ErrInvalidSession):
|
||||
|
||||
Reference in New Issue
Block a user